#197: security response headers and an explicitly restrictive CORS policy #252

Merged
fable-5 merged 1 commits from feat/197-security-headers into main 2026-07-30 21:41:38 +02:00
Collaborator

Closes-manually: #197 (issue closed after green pipeline, per repo convention).

Hand-rolled security-header middleware instead of helmet (supply-chain minimalism; every value deliberate): HSTS without includeSubDomains, nosniff, Referrer-Policy no-referrer, X-Frame-Options SAMEORIGIN (not DENY -- the plugin sandbox frame embeds same-origin and its CSP has no frame-ancestors, so this header governs), minimal deny-all Permissions-Policy.

CORS: no foreign origin is granted anything; only the APP_BASE_URL origin is echoed (browsers never consult CORS there, being same-origin), Vary: Origin on every response, no preflight handling needed.

Wired via AppModule MiddlewareConsumer so createTestApp boots the identical middleware. Tests: security-headers.e2e.test.ts (5 cases, incl. foreign origin gets no ACAO and headers on 404s) + frame assertion in plugins.e2e.db.test.ts. Docs: security.md rationale table.

Evidence: api full suite 469 green vs fresh dorfteich_t197, typecheck+lint green.

Closes-manually: #197 (issue closed after green pipeline, per repo convention). Hand-rolled security-header middleware instead of helmet (supply-chain minimalism; every value deliberate): HSTS without includeSubDomains, nosniff, Referrer-Policy no-referrer, X-Frame-Options SAMEORIGIN (not DENY -- the plugin sandbox frame embeds same-origin and its CSP has no frame-ancestors, so this header governs), minimal deny-all Permissions-Policy. CORS: no foreign origin is granted anything; only the APP_BASE_URL origin is echoed (browsers never consult CORS there, being same-origin), Vary: Origin on every response, no preflight handling needed. Wired via AppModule MiddlewareConsumer so createTestApp boots the identical middleware. Tests: security-headers.e2e.test.ts (5 cases, incl. foreign origin gets no ACAO and headers on 404s) + frame assertion in plugins.e2e.db.test.ts. Docs: security.md rationale table. Evidence: api full suite 469 green vs fresh dorfteich_t197, typecheck+lint green.
fable-5 added 1 commit 2026-07-30 17:10:51 +02:00
#197: security response headers and an explicitly restrictive CORS policy
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 5m12s
CI / Build container images (pull_request) Successful in 2m48s
CI / Auth e2e pack (pull_request) Successful in 7m50s
CI / Import/export fidelity gate (pull_request) Successful in 55s
e606b869d8
Hand-rolled middleware instead of helmet: the header set is small enough
to own, every value is a deliberate decision, and the api gains no
transitive dependency. HSTS (no includeSubDomains — the api cannot speak
for sibling subdomains), nosniff, Referrer-Policy no-referrer,
X-Frame-Options SAMEORIGIN (not DENY: the plugin sandbox frame embeds
same-origin and its CSP has no frame-ancestors, so this header governs),
and a minimal deny-all Permissions-Policy.

CORS grants no foreign origin anything; only the APP_BASE_URL origin is
ever echoed (where browsers do not consult CORS anyway), with
Vary: Origin on every response. No preflight handling — same-origin
requests never preflight, and cross-origin API access is cookie-less by
design (PAT/Bearer).

Wired via the AppModule MiddlewareConsumer so createTestApp boots the
identical middleware. Fences: security-headers.e2e.test.ts (header set,
foreign origin gets no ACAO) and a frame assertion in
plugins.e2e.db.test.ts (framing stays possible). Rationale table in
security.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ
fable-5 force-pushed feat/197-security-headers from e606b869d8 to 3c62b7b773 2026-07-30 17:13:27 +02:00 Compare
fable-5 merged commit 3c62b7b773 into main 2026-07-30 21:41:38 +02:00
Sign in to join this conversation.
No description provided.