Skip to main content

Module edge_headers

Module edge_headers 

Source
Expand description

PQC-aware edge-runtime header emitter (issue #550).

Emits per-platform header configuration files for Cloudflare Workers (wrangler-headers.toml), Netlify (_headers), and Vercel (vercel-headers.json) so the deployed site lands with TLS, Permissions-Policy, X-Content-Type-Options, Referrer-Policy and the site’s computed Content-Security-Policy already locked in.

§Scope (locked-in)

  • Static emit only. No live TLS probing at build time.
  • Five baseline headers. baseline_headers is the source of truth; per-target emitters render those keys/values into the platform-specific syntax. Anything else (cache-control, per-route headers) lives in the existing deploy adapter — the edge-headers emitter is intentionally orthogonal to deploy target generation.
  • CSP comes from the CSP plugin. The Content-Security-Policy value is sourced from crate::csp::computed_policy; the emitter does not recompute or hardcode the string. AC7.
  • PQC documentation comment in every emitted file naming the X25519+ML-KEM-768 hybrid key-exchange suite (CDN handles the actual negotiation — we just document and link). AC6.
  • Per-target overrides via [edge_headers.overrides] in ssg.toml; case-insensitive on the header key, last-write-wins per target. AC5.

§Per-page CSP (spec B4, v0.0.47 plan §3 item 2.4)

Pages that still carry inline blocks after the CSP plugin’s extraction pass (JSON-LD structured data, chiefly) get a per-path Content-Security-Policy entry in the Netlify _headers and Vercel vercel-headers.json outputs, built from crate::csp::page_policy — the hash-strict rendering of crate::csp::DEFAULT_CSP_POLICY_TEMPLATE with that page’s SHA-256 inline source hashes. Pages without inline blocks fall back to the global /* policy.

§Ordering contract

The build pipeline runs every plugin’s after_compile hook before the fused transform_html pass, so per-page hashes cannot be computed in after_compile (the CSP plugin’s inline extraction and the minifier have not run yet). Instead:

  1. after_compile emits the platform files with the global policy only (deterministic fallback, also the final state for sites with zero inline blocks) and resets this build’s per-page registry.
  2. transform_html — registered after CspPlugin and MinifyPlugin in register_default_plugins, so it observes the final shipped bytes of each page — records the page’s policy and re-emits the platform files. Any future transform plugin that injects inline <script>/<style> content must register before edge-headers or its blocks will not be hashed.

§Determinism

Per-page policies accumulate in a BTreeMap keyed by URL path, so rendered output is sorted regardless of rayon scheduling. Every page inserts its entry before writing a full snapshot under the same mutex, so the chronologically last write — the one that survives on disk — contains every page’s entry. No sidecar file is written and nothing extra ships in the site output (determinism.yml byte-hashes the result).

§File layout

dist/
├── _headers                           # Netlify (AC2)
└── .ssg/edge/
    ├── wrangler-headers.toml          # Cloudflare (AC1)
    └── vercel-headers.json            # Vercel (AC3)

Cloudflare and Vercel files live under .ssg/edge/ so they don’t clash with any user-managed wrangler.toml/vercel.json at the site root; the leading comment block on each file documents the intended merge path.

Structs§

EdgeHeadersPlugin
Postprocess plugin that emits per-platform edge header config.

Functions§

baseline_headers
Baseline header set emitted by every target.
merged_headers
Merges baseline headers with case-insensitive overrides.