@networkpro/web
Version:
Locking Down Networks, Unlocking Confidence™ | Security, Networking, Privacy — Network Pro Strategies
160 lines (135 loc) • 4.96 kB
JavaScript
/* ==========================================================================
src/hooks.server.js
Copyright © 2025-2026 Network Pro Strategies (Network Pro™)
SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
This file is part of Network Pro.
========================================================================== */
import { isProbelyScanner } from '$lib/security/probely.js';
import { detectEnvironment } from '$lib/utils/env.js';
/**
* SvelteKit server hook to set Content Security Policy (CSP) header.
* @type {import('@sveltejs/kit').Handle}
*/
export async function handle({ event, resolve }) {
/**
* 🔍 Probely scanner allowlisting
* - Robust UA check (case‑insensitive)
* - Normalized X‑Forwarded‑For parsing
* - Avoids false positives
* @see https://help.probely.com/en/articles/5112461/
*/
/** @type {string} */
const userAgent = event.request.headers.get('user-agent') || '';
/** @type {string} */
const remoteIp =
event.request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || '';
const isProbely = isProbelyScanner({ ua: userAgent, ip: remoteIp });
if (isProbely) {
console.info('[Probely Bypass] Matched scanner request:', {
ip: remoteIp,
ua: userAgent,
});
}
const response = await resolve(event);
const env = detectEnvironment(event.url.hostname);
const { isAudit, isDebug, isTest, isProd } = env;
const reportUri =
isProd && !isTest && !isAudit
? 'https://csp.netwk.pro/.netlify/functions/csp-report'
: '/api/mock-csp';
console.log('[CSP] Report URI set to:', reportUri);
const cspDirectives = [
"default-src 'self';",
"script-src 'self' 'unsafe-inline' https://us.i.posthog.com https://us-assets.i.posthog.com;",
"style-src 'self' 'unsafe-inline';",
"img-src 'self' data:;",
"connect-src 'self' https://us.i.posthog.com https://us-assets.i.posthog.com;",
"font-src 'self' data:;",
"form-action 'self';",
"base-uri 'self';",
"object-src 'none';",
"frame-ancestors 'none';",
'upgrade-insecure-requests;',
];
// 🧪 Looser CSP for local/CI test environments
if (isDebug) {
cspDirectives[1] =
"script-src 'self' 'unsafe-inline' 'unsafe-eval' http://localhost:* ws://localhost:*;";
cspDirectives[2] = "style-src 'self' 'unsafe-inline' http://localhost:*;";
cspDirectives[3] = "img-src 'self' data: http://localhost:*;";
cspDirectives[4] =
"connect-src 'self' http://localhost:* ws://localhost:* https://us.i.posthog.com https://us-assets.i.posthog.com;";
}
// 🧩 Hardened CSP for audit environment — no analytics, no CSP reporting
if (isAudit) {
cspDirectives[1] = "script-src 'self' 'unsafe-inline';";
cspDirectives[2] = "style-src 'self' 'unsafe-inline';";
cspDirectives[3] = "img-src 'self' data:;";
cspDirectives[4] = "connect-src 'self';";
}
// 📋 Add reporting for environments that support it
const shouldReport = !isAudit && !isTest;
if (shouldReport) {
cspDirectives.push(`report-uri ${reportUri};`, 'report-to csp-endpoint;');
response.headers.set(
'Report-To',
JSON.stringify({
group: 'csp-endpoint',
max_age: 10886400, // 18 weeks
endpoints: [{ url: reportUri }],
include_subdomains: true,
}),
);
}
// ✅ Apply CSP — enforce in prod or audit, report-only in dev/test
const cspHeader =
(isProd || isAudit) && !isTest
? 'Content-Security-Policy'
: 'Content-Security-Policy-Report-Only';
response.headers.set(cspHeader, cspDirectives.join(' '));
// Log applied CSP headers in debug/audit/test
if (isDebug || isAudit) {
console.info(`[CSP] Applied header: ${cspHeader}`);
console.info(`[CSP] Policy:`, cspDirectives.join(' '));
console.info(`[CSP] Reporting to: ${reportUri}`);
}
// Standard security headers
response.headers.set(
'Permissions-Policy',
[
'fullscreen=(self)',
'sync-xhr=()',
'camera=()',
'microphone=()',
'geolocation=()',
'clipboard-read=()',
'clipboard-write=(self)',
'payment=()',
'usb=()',
'hid=()',
'gamepad=()',
'serial=()',
'publickey-credentials-get=()',
'browsing-topics=()',
].join(', '),
);
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
response.headers.set('X-Frame-Options', 'DENY');
if (!isTest) {
response.headers.set(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains;',
);
}
return response;
}
/**
* SvelteKit server-side error handler to log SSR errors.
* @type {import('@sveltejs/kit').HandleServerError}
*/
export function handleError({ error, event }) {
console.error('🔴 SSR Error in route:', event.url.pathname);
console.error(error);
return { message: 'A server-side error occurred' };
}