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_headersis 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-Policyvalue is sourced fromcrate::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]inssg.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:
after_compileemits 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.transform_html— registered afterCspPluginandMinifyPlugininregister_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 beforeedge-headersor 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§
- Edge
Headers Plugin - 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.