security updates
This commit is contained in:
39
src/hooks.server.js
Normal file
39
src/hooks.server.js
Normal file
@@ -0,0 +1,39 @@
|
||||
/** @type {import('@sveltejs/kit').Handle} */
|
||||
export async function handle({ event, resolve }) {
|
||||
const response = await resolve(event);
|
||||
|
||||
// ── Security headers ────────────────────────────────────────────────────────
|
||||
// Prevent clickjacking
|
||||
response.headers.set('X-Frame-Options', 'SAMEORIGIN');
|
||||
|
||||
// Prevent MIME-type sniffing
|
||||
response.headers.set('X-Content-Type-Options', 'nosniff');
|
||||
|
||||
// Control referrer information
|
||||
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
|
||||
|
||||
// Restrict browser features
|
||||
response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
|
||||
|
||||
// Content Security Policy
|
||||
// - default-src 'self': only load resources from our own origin
|
||||
// - script-src 'self' 'unsafe-inline': SvelteKit needs inline scripts for hydration
|
||||
// - style-src 'self' 'unsafe-inline' fonts.googleapis.com: inline styles + Google Fonts CSS
|
||||
// - font-src 'self' fonts.gstatic.com: Google Fonts files
|
||||
// - img-src 'self' data:: allow data URIs for any inline images
|
||||
// - connect-src 'self': API calls only to our own origin
|
||||
// - frame-ancestors 'none': belt-and-suspenders against clickjacking
|
||||
response.headers.set('Content-Security-Policy', [
|
||||
"default-src 'self'",
|
||||
"script-src 'self' 'unsafe-inline'",
|
||||
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
|
||||
"font-src 'self' https://fonts.gstatic.com",
|
||||
"img-src 'self' data:",
|
||||
"connect-src 'self'",
|
||||
"frame-ancestors 'none'",
|
||||
"base-uri 'self'",
|
||||
"form-action 'self'",
|
||||
].join('; '));
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
import { getAdminStats, listReports, listRecentPersonalitiesAdmin, listContactMessages } from '$lib/server/db.js';
|
||||
|
||||
export async function load({ url }) {
|
||||
const reportFilter = url.searchParams.get('reports') ?? 'open';
|
||||
const messageFilter = url.searchParams.get('messages') ?? 'unread';
|
||||
const adminQ = url.searchParams.get('q') ?? '';
|
||||
const VALID_REPORT_FILTERS = ['open', 'dismissed', 'all'];
|
||||
const VALID_MESSAGE_FILTERS = ['unread', 'all'];
|
||||
|
||||
const reportFilter = VALID_REPORT_FILTERS.includes(url.searchParams.get('reports') ?? '')
|
||||
? url.searchParams.get('reports') : 'open';
|
||||
const messageFilter = VALID_MESSAGE_FILTERS.includes(url.searchParams.get('messages') ?? '')
|
||||
? url.searchParams.get('messages') : 'unread';
|
||||
const adminQ = (url.searchParams.get('q') ?? '').slice(0, 200);
|
||||
const adminPage = Math.max(1, parseInt(url.searchParams.get('page') ?? '1'));
|
||||
|
||||
const stats = getAdminStats();
|
||||
|
||||
@@ -276,8 +276,7 @@
|
||||
{msg.name || 'Anonymous'}
|
||||
</span>
|
||||
{#if msg.email}
|
||||
<a href="mailto:{msg.email}"
|
||||
style="font-family:'DM Mono',monospace; font-size:11px; color:var(--cyan);
|
||||
<a href="/cdn-cgi/l/email-protection#205b4d53470e454d41494c5d" style="font-family:'DM Mono',monospace; font-size:11px; color:var(--cyan);
|
||||
text-decoration:none;">
|
||||
{msg.email}
|
||||
</a>
|
||||
@@ -643,5 +642,3 @@
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -2,13 +2,16 @@ import { listPersonalities, getDistinctManufacturers, getManufacturerCounts } fr
|
||||
import { MANUFACTURER_SEEDS } from '$lib/server/manufacturers.js';
|
||||
|
||||
const VALID_LIMITS = [12, 24, 48, 96];
|
||||
const VALID_SORTS = ['newest', 'popular'];
|
||||
const MAX_Q_LEN = 200;
|
||||
|
||||
export async function load({ url }) {
|
||||
const q = url.searchParams.get('q') ?? '';
|
||||
const manufacturer = url.searchParams.get('manufacturer') ?? '';
|
||||
const sort = url.searchParams.get('sort') ?? 'newest';
|
||||
const q = (url.searchParams.get('q') ?? '').slice(0, MAX_Q_LEN);
|
||||
const manufacturer = (url.searchParams.get('manufacturer') ?? '').slice(0, 128);
|
||||
const sortParam = url.searchParams.get('sort') ?? 'newest';
|
||||
const sort = VALID_SORTS.includes(sortParam) ? sortParam : 'newest';
|
||||
const page = Math.max(1, parseInt(url.searchParams.get('page') ?? '1'));
|
||||
const view = url.searchParams.get('view') ?? 'cards';
|
||||
const view = url.searchParams.get('view') === 'table' ? 'table' : 'cards';
|
||||
const limitParam = parseInt(url.searchParams.get('limit') ?? '24');
|
||||
const limit = VALID_LIMITS.includes(limitParam) ? limitParam : 24;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user