This document covers the frontend's security posture and the deployment checklist for exposing Insight to the public internet (without VPN).
The frontend is a public-facing SPA that authenticates users via OIDC
(Authorization Code + PKCE) against a customer-provided issuer and calls a
single backend through /api/*. Trust boundary: the FE itself is untrusted —
all authorization decisions live on the backend; the FE only attaches the
bearer token and the tenant id.
Access tokens are stored in sessionStorage via oidc-client-ts's
WebStorageStateStore. This is the standard SPA tradeoff:
sessionStorageis per-tab and cleared when the tab closes — better thanlocalStoragefor blast radius.- Tokens are accessible from JavaScript, so any XSS = token exfiltration. The CSP below is the primary mitigation; keep it tight.
- HttpOnly cookies would be stronger, but require a backend session bridge — out of scope for the current architecture.
Defined in Dockerfile:
| Header | Value | Why |
|---|---|---|
Content-Security-Policy |
see Dockerfile | Restrict script/style/connect sources, block inline scripts, prevent framing |
X-Frame-Options |
DENY |
Clickjacking defense for legacy browsers (CSP frame-ancestors covers modern ones) |
X-Content-Type-Options |
nosniff |
Prevent MIME-sniffing attacks on uploaded/static content |
Referrer-Policy |
strict-origin-when-cross-origin |
Don't leak full URLs (with query params) to third parties |
Strict-Transport-Security |
max-age=31536000; includeSubDomains |
Force HTTPS once a client has connected once |
Permissions-Policy |
camera=(), microphone=(), geolocation=() |
Disable powerful APIs the app doesn't use |
Cross-Origin-Opener-Policy |
same-origin |
Isolate browsing context — defense against cross-window leaks (Spectre, tab-napping) |
Cross-Origin-Resource-Policy |
same-origin |
Block other origins from embedding our resources |
style-src 'unsafe-inline'is required for React inline styles and recharts. Tightening this requires a nonce-based pipeline — tracked as a follow-up.connect-srcandframe-srcare templated at container start bydocker-entrypoint.sh: whenOIDC_ISSUERis set, the issuer's origin is substituted in (tight). When not set, falls back to broadhttps:(still better than*). The substitution covers silent-renew iframe and token endpoint requests.- After deployment, verify silent renew works (token auto-refreshes after 5 min)
— if the OIDC issuer sets
X-Frame-Options: DENYon its authorize endpoint, silent renew will fail and you'll need a different refresh strategy.
build.sourcemap: false— production bundles never ship source maps.esbuild.drop: ['debugger']—debuggerstatements are stripped.console.*calls are gated behindimport.meta.env.DEVand tree-shaken in production. Afternpm run build, verify with:grep -c "AuthPlugin\|Auto-discovered\|OIDC skipped" dist/assets/*.js # Expect: 0 ls dist/assets/*.map 2>/dev/null # Expect: nothing
-
.envon the build host does NOT containVITE_DEV_USER_EMAILorVITE_ENABLE_MOCKS=true. These are dev-only and tree-shaken in prod, but double-check there's no DEV build going to production. - Container is served behind HTTPS-terminating reverse proxy. HSTS only makes sense over TLS.
-
window.__OIDC_CONFIG__is injected at container startup with the real issuer URL, client id, and redirect URI. No fallback to mock auth in prod. - Backend (
api-gateway,analytics-api,identity-resolution) validates theX-Tenant-IDheader against the JWT's tenant claim. When the FE sends this header (currently reserved — wired throughauthStore.tenantIdinsrc/api/fetch-with-auth.ts), without server-side validation a logged-in user can read other tenants by editing the header in DevTools. - Backend rate-limits unauthenticated and authenticated endpoints separately. The FE has no rate-limiting and shouldn't.
- Verify silent renew works in staging for ≥10 minutes of idle session.
- Confirm CSP doesn't break recharts / shadcn / @base-ui styling on every screen.
- Run
npm audit --omit=dev— no high-severity findings.
Security issues: contact the Insight team lead. Do not file public GitHub issues for vulnerabilities.