mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 06:52:04 +00:00
security: HSTS preload + Withings CSP gating + SW URL trust + manifest hygiene
HSTS gains `; preload` so the domain becomes eligible for the Chromium preload list. Withings CSP host moves from global to a path-gated entry (mirrors the existing AI-host gating) — closes a DOM-XSS exfil shape on every non-Withings page. `getClientIp` warns once per process when `TRUST_PROXY_HOPS` and the XFF chain length disagree, and exposes a tagged `getClientIpOrTrustWarning` helper so future callers can route to a tighter universal rate-limit bucket when the chain looks tampered with. Service worker now resolves push-notification payload URLs against `self.location.origin` and rejects off-origin redirects (falls back to `/`). The German-only offline fallback HTML body was replaced with a language-neutral icon + 'Offline' wordmark so EN / FR / ES / IT / PL users don't see German on a cold offline boot. Manifest gained `"scope": "/"`, `"id": "/?source=pwa"`, and `"display_override": ["standalone"]` so future sub-path deploys don't accidentally inherit root scope and install-prompt heuristics anchor on a stable id. +16 new test cases across two new files covering HSTS preload, CSP gating on/off, the once-per-process warning, the tagged helper's 4 shape branches, SW same-origin guard, offline-locale-neutrality, and manifest hygiene. Existing proxy/SW suites stay green.
This commit is contained in:
1 parent
46fe638741
commit
6885703505
7 files changed
+420
-14
No files matched your search
+20
-7
@@ -234,22 +234,35 @@ export function proxy(request: NextRequest) {
|
||||
"camera=(), microphone=(), geolocation=()",
|
||||
);
|
||||
|
||||
// CSP — permissive in dev, strict in production. AI provider hosts
|
||||
// (OpenAI / chatgpt.com) are gated to /settings/ai/** because that is
|
||||
// the only surface a browser fetch is needed (V3 audit: blanket
|
||||
// chatgpt.com on /auth/login is a DOM-XSS exfil channel).
|
||||
// CSP — permissive in dev, strict in production. Third-party hosts in
|
||||
// `connect-src` are gated to the surfaces that actually need them so a
|
||||
// DOM-XSS on an unrelated page can't exfiltrate to them (V3 audit:
|
||||
// blanket chatgpt.com on /auth/login was a DOM-XSS exfil channel).
|
||||
//
|
||||
// F-5 (mobile security audit, 2026-05-16): `wbsapi.withings.net` used
|
||||
// to live in the global `connect-src` and shipped on every page. The
|
||||
// Withings client lives server-side, so the browser never needs to
|
||||
// reach it from a non-Withings surface; mirror the AI gating shape.
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
const cspReportEndpoint = "/api/monitoring/csp-report";
|
||||
const isAiSettingsRoute = pathname.startsWith("/settings/ai");
|
||||
const aiConnectSrc = isAiSettingsRoute
|
||||
? " https://api.openai.com https://chatgpt.com"
|
||||
: "";
|
||||
const isWithingsRoute =
|
||||
pathname.startsWith("/settings/integrations/withings") ||
|
||||
pathname.startsWith("/api/withings/");
|
||||
const withingsConnectSrc = isWithingsRoute
|
||||
? " https://wbsapi.withings.net"
|
||||
: "";
|
||||
const csp = isDev
|
||||
? `default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://www.gravatar.com; connect-src 'self'; font-src 'self';`
|
||||
: `default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://www.gravatar.com; connect-src 'self'${aiConnectSrc} https://wbsapi.withings.net; font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; worker-src 'self'; report-uri ${cspReportEndpoint}; report-to csp-endpoint;`;
|
||||
: `default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://www.gravatar.com; connect-src 'self'${aiConnectSrc}${withingsConnectSrc}; font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; worker-src 'self'; report-uri ${cspReportEndpoint}; report-to csp-endpoint;`;
|
||||
response.headers.set("Content-Security-Policy", csp);
|
||||
|
||||
// Production-only headers
|
||||
// Production-only headers. HSTS carries `preload` so the domain stays
|
||||
// eligible for the Chromium preload list — closes the first-visit MITM
|
||||
// window on hostile networks (F-5, mobile security audit 2026-05-16).
|
||||
if (!isDev) {
|
||||
response.headers.set(
|
||||
"Reporting-Endpoints",
|
||||
@@ -257,7 +270,7 @@ export function proxy(request: NextRequest) {
|
||||
);
|
||||
response.headers.set(
|
||||
"Strict-Transport-Security",
|
||||
"max-age=31536000; includeSubDomains",
|
||||
"max-age=31536000; includeSubDomains; preload",
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user