Skip to content

feat(helmet): HTTP response security headers#49

Merged
parkrevil merged 7 commits into
mainfrom
feat/helmet
May 11, 2026
Merged

feat(helmet): HTTP response security headers#49
parkrevil merged 7 commits into
mainfrom
feat/helmet

Conversation

@parkrevil

Copy link
Copy Markdown
Contributor

Helmet middleware package — HTTP response security headers engine.

parkrevil and others added 7 commits April 26, 2026 00:08
Adds a new framework-agnostic security headers package matching helmet 8.x
parity (14 options + 7 aliases) plus modern standards: Permissions-Policy,
Reporting-Endpoints, NEL, Integrity-Policy, Clear-Site-Data, COEP,
Document-Policy/Isolation-Policy, Trusted Types, per-request nonce
injection, and batched validation that aggregates every violation into a
single throw.

Static helpers (Helmet.csp / hsts / permissionsPolicy / referrerPolicy /
xFrameOptions / xContentTypeOptions / originAgentCluster / crossOrigin{Opener,
Resource,Embedder}Policy) provide tree-shake-friendly equivalents of helmet's
sub-middleware. Companion tooling: 11 environment presets (strict, api, spa,
observatoryAPlus, amp, oauth, kisa, acsc, bsi, ncsc, ipa), helmet.js v8/v9
fromHelmetOptions migration, csp-evaluator-equivalent lintCsp, parseCspReport
(legacy + Reporting API normalisation with strict body/Content-Type/timeout
hardening), hashFromString (Web Crypto SRI), Csp keyword constants, and
RFC 9651 structured-fields serialisation.

Companion: extends @zipbul/shared HttpHeader enum with 30+ security header
names. PLAN.md updated with @zipbul/result usage policy, deep-freeze
semantics, derive() re-validation contract, parseCspReport input validation,
xPoweredBy/removeHeaders conflict resolution, and Permissions-Policy
registry reconcile table.

Tests: 136 pass / 0 fail / 256 expect calls across 13 files. Covers full
helmet 8.x option matrix (defaults, false to disable, every value variant),
nonce fallback safety (regression for default-src preservation), apply()
semantics (304 skip, set-if-absent vs always-overwrite, multi Set-Cookie,
Response.redirect), prototype-pollution guards, batched validation
aggregation, CSP cross-reference of report-to with reportingEndpoints.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the legacy agent guidance files from the repository.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ader

Strip framework adapters, presets, non-HTTP serializers, and helmet@npm
migration shim — these belong in separate packages, not in a runtime-
agnostic security headers engine.

- Remove src/adapters/ (next/sveltekit/remix/astro/hono/elysia/electron)
- Remove src/serializers/ (meta-tag/tauri/extension-manifest)
- Remove src/presets.ts and src/migration.ts
- Remove safe-regex2 dep (input length cap + bounded regex already guard ReDoS)
- Collapse 17 subpath exports to a single `.` entry — every subpath funneled
  through Helmet.create() so tree-shake benefit was zero, only overhead

SRP — one HTTP header per directory:
- Split src/simple-headers/ → 10 dirs (referrer-policy, x-frame-options,
  x-content-type-options, x-dns-prefetch-control, x-permitted-cross-
  domain-policies, x-xss-protection, x-download-options, origin-agent-
  cluster, timing-allow-origin, x-robots-tag)
- Split src/cross-origin/ → coop/, coep/, corp/
- Promote shared HeaderEntry type to src/header-entry.ts

Bug fix:
- CSP nonce strip leaves no orphan ' ;' between directives (helmet.ts)

Tests + docs:
- Add test/golden.test.ts (Default-ON snapshot lock + nonce invariants)
- Add test/new-surface.test.ts (Helmet.endpoints + nonce edge cases +
  Response 304/opaque + Set-Cookie passthrough + CSP fuzz)
- Add test/helmet.bench.test.ts (microbench for hot paths)
- Rewrite README.md and README.ko.md to scope-accurate quickstart
- Drop helmet-parity test (express-helmet compat is out of scope)

122 pass / 0 fail
Standards-driven fixes verified directly against W3C/IETF/WHATWG sources:

- sf-string rejects non-ASCII (RFC 9651 §3.3.3)
- sf-decimal enforces 12-digit integer cap (RFC 9651 §3.3.2)
- frame-ancestors restricted to scheme/host/'self'/'none' — rejects
  nonces, hashes, and all keywords other than 'self'/'none' (CSP3 §6.4.2)
- Referrer-Policy rejects empty token list (W3C §4.1: 1#policy-token)
- X-Permitted-Cross-Domain-Policies adds 'by-ftp-filename'
- Timing-Allow-Origin restricted to origin form, no path/fragment
  (Resource Timing §3.5.2)
- Reporting endpoint URL strips fragment (W3C Reporting-1 §3.6)
- Permissions-Policy: drop 'publickey-credentials-create' (not in W3C
  registry); add 22 missing registry features (ch-ua-*, document-domain,
  window-placement, focus-without-user-activation, conversion-measurement,
  join-ad-interest-group, manual-text, monetization, run-ad-auction,
  sync-script, trust-token-redemption, vertical-scroll). Tier names
  mirror registry sections (Standardized / Proposed / Experimental /
  Retired). ch-ua-* deliberately excluded from default-deny set.
- COEP / COOP support `report-to="<endpoint>"` parameter, cross-validated
  against reportingEndpoints (HTML §7.1.3.1 / §7.1.4.1)
- X-Robots-Tag directive grammar validation (Google Search Central spec):
  boolean rules, max-snippet/max-image-preview/max-video-preview/
  unavailable_after value forms, optional bot-name prefix, CRLF guard
- document-policy: prototype-chain check for Object.create() attacks
- Removed dead nonce control-char branch in csp/source-validate
  (NONCE_RE alphabet excludes control chars by construction)

Coverage: line 79.5% → 100.00%, function ~80% → 99.74%
Tests: 124 → 315 (1005 expect calls), 0 fail
Adds property-based fuzz tests for sf-string round-trip, decimal range,
malformed CSP source rejection, and CR/LF absence in emitted headers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts:
#	bun.lock
#	packages/shared/src/enums/http-header.ts
@parkrevil parkrevil merged commit d669f0f into main May 11, 2026
1 check passed
@parkrevil parkrevil deleted the feat/helmet branch May 11, 2026 03:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant