Skip to content

Latest commit

 

History

History
101 lines (70 loc) · 8.28 KB

File metadata and controls

101 lines (70 loc) · 8.28 KB

Security headers and Content-Security-Policy

AgentPay applies its baseline browser security posture from next.config.ts by calling defaultSecurityHeaders() in src/lib/securityHeaders.ts for every route (/:path*). The header map is generated at build time so the shipped Content-Security-Policy (CSP) matches the same API origin the client code will use.

This document is a reference for contributors changing headers, CSP directives, external integrations, or the theme pre-paint script.

Where the headers are defined

  • src/lib/securityHeaders.ts owns the full header map and CSP directive list.
  • next.config.ts resolves the API base once with resolveApiBase() and passes it into defaultSecurityHeaders().
  • src/app/layout.tsx contains the inline theme pre-paint script that explains part of the current CSP tradeoff.

Response header map

defaultSecurityHeaders() returns these headers for every Next.js response:

Header Current value / behavior Purpose
Content-Security-Policy Generated by buildCsp() Restricts where scripts, styles, images, fonts, fetches, forms, frames, objects, and base URLs can load or execute.
X-Content-Type-Options nosniff Prevents browsers from MIME-sniffing a response away from its declared Content-Type.
Referrer-Policy strict-origin-when-cross-origin Sends full referrers on same-origin navigation, but only the origin for cross-origin HTTPS requests and no referrer on HTTPS-to-HTTP downgrades.
X-Frame-Options DENY Legacy clickjacking defense that prevents the dashboard from being framed. This complements CSP frame-ancestors 'none'.
Permissions-Policy camera=(), microphone=(), geolocation=(), payment=(), browsing-topics=(), interest-cohort=() Denies sensitive browser capabilities and tracking-related APIs by default.
Strict-Transport-Security max-age=63072000; includeSubDomains; preload in production only Tells browsers to use HTTPS for this host for two years, including subdomains, and opts into preload eligibility. It is omitted in development so local browsers do not cache an HTTPS upgrade for dev servers.

CSP directive reference

buildCsp() currently emits the following directives. Keep this table in sync with the directives object in src/lib/securityHeaders.ts.

Directive Current sources Why it exists
default-src 'self' Baseline fallback: resources are same-origin unless a more specific directive below allows something else.
script-src Production: 'self' 'unsafe-inline'; development: 'self' 'unsafe-inline' 'unsafe-eval' Allows dashboard scripts from this origin. 'unsafe-inline' is currently required because the app ships inline scripts, including Next.js hydration/bootstrap output when CSP is applied through next.config.ts headers and the theme pre-paint script in src/app/layout.tsx. 'unsafe-eval' is development-only for Next.js Fast Refresh and related dev tooling. Do not add 'unsafe-eval' to production.
style-src 'self' 'unsafe-inline' Allows same-origin styles plus inline style tags generated by Next.js and next/font.
font-src 'self' data: Allows bundled same-origin fonts and data: font URLs.
img-src 'self' data: Allows same-origin images and small inline data: images.
connect-src 'self' plus the resolved API origin Allows browser fetch/WebSocket-style connections to the dashboard origin and the AgentPay API origin derived from NEXT_PUBLIC_AGENTPAY_API_BASE.
frame-ancestors 'none' Modern clickjacking defense that prevents any parent page from embedding the dashboard.
form-action 'self' Restricts form submissions to the dashboard origin.
base-uri 'self' Prevents injected <base> tags from rewriting relative URLs to an attacker-controlled origin.
object-src 'none' Blocks legacy plugin/embed/object execution surfaces.

The policy intentionally does not include navigate-to, so normal top-level navigation from links such as external documentation or Stellar links remains possible.

How connect-src tracks the API origin

The API base URL is resolved in next.config.ts with resolveApiBase(). That value is passed to defaultSecurityHeaders(), and buildCsp() calls originOf(apiBase) before adding it to connect-src.

Only the origin is used. For example, https://api.example.com/v1 becomes https://api.example.com in CSP. This keeps the CSP aligned with browser origin checks and with the actual backend origin used by the frontend API client.

If apiBase cannot be parsed as a URL, originOf() falls back to the default local API origin from DEFAULT_API_BASE. This keeps the generated CSP valid instead of emitting an invalid source expression.

Inline script and style tradeoffs

'unsafe-inline' is present in two places for specific reasons:

  1. script-src includes 'unsafe-inline' because the app uses CSP from next.config.ts rather than request-time nonce middleware, and because src/app/layout.tsx injects a blocking pre-paint theme script. That script reads localStorage, applies the dark or light class to <html> before the first paint, and avoids a flash of the wrong theme before React hydration.
  2. style-src includes 'unsafe-inline' because Next.js and next/font can inject inline style tags needed for rendering.

'unsafe-eval' appears only when isDev is true. It supports the development server experience, including Fast Refresh. Production builds omit it.

If the theme script is ever replaced with a nonce-compatible or external-script approach, revisit script-src and remove 'unsafe-inline' only after confirming Next.js hydration/bootstrap scripts and the pre-paint behavior still work under the new policy.

Safely adding an allowed origin

Before adding a source, decide whether it is actually needed by browser-enforced CSP:

  • API calls, analytics beacons, EventSource, or WebSocket connections usually require connect-src.
  • Images require img-src.
  • Fonts require font-src.
  • Scripts require script-src and should be avoided unless the origin is trusted and stable.
  • Top-level link navigation does not require a CSP source unless the app adds navigate-to later.

Recommended workflow:

  1. Prefer configuration-derived origins. For the primary backend, set NEXT_PUBLIC_AGENTPAY_API_BASE; do not hard-code a duplicate API origin in CSP.
  2. Add the narrowest source expression to the specific directive in src/lib/securityHeaders.ts. Prefer a full origin such as https://api.example.com over a scheme or wildcard.
  3. Keep development-only relaxations behind isDev so production remains stricter.
  4. Add or update tests in src/__tests__/securityHeaders.test.ts when behavior changes.
  5. Update this document in the same PR and verify every documented directive/header still exists in source.
  6. Run npm run lint, npm run build, and a source cross-check such as rg -n "default-src|script-src|style-src|font-src|img-src|connect-src|frame-ancestors|form-action|base-uri|object-src|Content-Security-Policy|X-Content-Type-Options|Referrer-Policy|X-Frame-Options|Permissions-Policy|Strict-Transport-Security" src/lib/securityHeaders.ts next.config.ts docs/security-headers.md.

Safely relaxing a directive

Relaxing CSP or hardening headers should be treated as a security-sensitive change:

  • Document the product requirement and why a narrower alternative is not enough.
  • Avoid broad wildcards such as *, https:, or data: unless the resource type truly requires them.
  • Never add 'unsafe-eval' to production script-src without a clear migration plan.
  • Never remove both X-Frame-Options: DENY and frame-ancestors 'none' unless the dashboard is intentionally becoming embeddable; if embedding is required, allow only the exact parent origins.
  • Re-run the source cross-check and update this reference doc so future contributors can audit the change.

Maintenance checklist

When changing src/lib/securityHeaders.ts, confirm:

  • Every emitted response header is documented above.
  • Every emitted CSP directive is documented above.
  • Development-vs-production differences are described.
  • connect-src still reflects the configured API origin.
  • The relationship between script-src 'unsafe-inline' and the layout pre-paint script remains accurate.