SecurityHeadersInterceptor CSP/HSTS/X-Frame defaults drift from baseline #3

Closed
opened 2026-04-25 13:24:09 +02:00 by uwe.admin · 5 comments
Owner

Location: include/oatpp-authkit/interceptor/SecurityHeadersInterceptor.hpp:22-37

The shipped CSP allows 'unsafe-inline' on script-src and style-src; X-Frame-Options is SAMEORIGIN; img-src / connect-src use wildcards; no Permissions-Policy header is sent. The documented baseline (docs/security-baseline.md) prescribes strict defaults: no unsafe-inline, frame-ancestors 'none', no wildcard https: in img-src, no wss:/ws: in connect-src, X-Frame-Options: DENY, plus a Permissions-Policy header.

Either align the interceptor with the baseline or expose constructor args so consumers can opt in. From audit #1.

**Location:** `include/oatpp-authkit/interceptor/SecurityHeadersInterceptor.hpp:22-37` The shipped CSP allows `'unsafe-inline'` on `script-src` and `style-src`; `X-Frame-Options` is `SAMEORIGIN`; `img-src` / `connect-src` use wildcards; no `Permissions-Policy` header is sent. The documented baseline (`docs/security-baseline.md`) prescribes strict defaults: no `unsafe-inline`, `frame-ancestors 'none'`, no wildcard `https:` in `img-src`, no `wss:`/`ws:` in `connect-src`, `X-Frame-Options: DENY`, plus a `Permissions-Policy` header. Either align the interceptor with the baseline or expose constructor args so consumers can opt in. From audit #1.
Author
Owner

Agent Evaluation

Feasibility: Straightforward header rewrite plus optional constructor knobs. The interceptor is a single 40-line class with no consumers depending on its CSP value (fewo-webapp already overrides it via CspInterceptor, palibu/webapp-template/scaffold use it as-is so will pick up whatever ships). The risk is breaking a downstream that quietly relied on the lax defaults — but the explicit purpose of this library is hardened defaults, so tightening is the right direction.

Impact: Medium. Closes a doc-vs-code gap that quietly weakens every consumer that doesn't override. Strict defaults mean apps inherit the protection without thinking; opt-in for relaxations.

Effort: Small.

Recommendation: Accept.

Implementation plan

  1. Align CSP with docs/security-baseline.md: drop 'unsafe-inline' from script-src/style-src, drop https://unpkg.com, narrow img-src to 'self' data:, narrow connect-src to 'self', set frame-ancestors 'none'.
  2. X-Frame-Options: DENY instead of SAMEORIGIN (matches frame-ancestors 'none').
  3. Add Permissions-Policy header per baseline (accelerometer=(), camera=(), geolocation=(), …).
  4. Strict-Transport-Security — keep max-age=63072000 but drop includeSubDomains from the default (apex-clobbering risk noted in audit #1). Consumers can opt back in.
  5. Constructor knob for the rare consumer that genuinely needs something looser (Swagger UI subtree, embeddable widgets). One ctor argument: struct CspOverride { std::string scriptSrc, styleSrc, imgSrc, connectSrc, frameAncestors; } — fields default to empty meaning "use the strict baseline".
  6. Testtest/test_security_headers.cpp asserting (a) default config emits no 'unsafe-inline', (b) overrides reach the wire. Reuse the harness bootstrapped in #2.

Decision needed

Check one (edit this comment):

  • Option A — Strict baseline only — no constructor knobs; consumers that want looser CSP write their own interceptor (the fewo-webapp pattern via CspInterceptor). Smallest API surface.
  • Option B — Strict baseline + CspOverride ctor — keeps the library useful for consumers that want one knob without writing a new interceptor. Slightly larger API; aligns with how downstream typically wants "strict + one exception".
  • Option C — Per-path policy hookIAuthPolicy::cspOverride(path) so consumers can relax CSP for /swagger/* etc. without writing a new interceptor. Most flexible but adds another virtual to the policy interface.
## Agent Evaluation **Feasibility:** Straightforward header rewrite plus optional constructor knobs. The interceptor is a single 40-line class with no consumers depending on its CSP value (fewo-webapp already overrides it via `CspInterceptor`, palibu/webapp-template/scaffold use it as-is so will pick up whatever ships). The risk is breaking a downstream that *quietly* relied on the lax defaults — but the explicit purpose of this library is hardened defaults, so tightening is the right direction. **Impact:** Medium. Closes a doc-vs-code gap that quietly weakens every consumer that doesn't override. Strict defaults mean apps inherit the protection without thinking; opt-in for relaxations. **Effort:** Small. **Recommendation:** Accept. ### Implementation plan 1. **Align CSP** with `docs/security-baseline.md`: drop `'unsafe-inline'` from `script-src`/`style-src`, drop `https://unpkg.com`, narrow `img-src` to `'self' data:`, narrow `connect-src` to `'self'`, set `frame-ancestors 'none'`. 2. **`X-Frame-Options: DENY`** instead of `SAMEORIGIN` (matches `frame-ancestors 'none'`). 3. **Add `Permissions-Policy`** header per baseline (`accelerometer=(), camera=(), geolocation=(), …`). 4. **`Strict-Transport-Security`** — keep `max-age=63072000` but drop `includeSubDomains` from the *default* (apex-clobbering risk noted in audit #1). Consumers can opt back in. 5. **Constructor knob** for the rare consumer that genuinely needs something looser (Swagger UI subtree, embeddable widgets). One ctor argument: `struct CspOverride { std::string scriptSrc, styleSrc, imgSrc, connectSrc, frameAncestors; }` — fields default to empty meaning "use the strict baseline". 6. **Test** — `test/test_security_headers.cpp` asserting (a) default config emits no `'unsafe-inline'`, (b) overrides reach the wire. Reuse the harness bootstrapped in #2. ### Decision needed Check one (edit this comment): - [ ] **Option A — Strict baseline only** — no constructor knobs; consumers that want looser CSP write their own interceptor (the fewo-webapp pattern via `CspInterceptor`). Smallest API surface. - [x] **Option B — Strict baseline + `CspOverride` ctor** — keeps the library useful for consumers that want one knob without writing a new interceptor. Slightly larger API; aligns with how downstream typically wants "strict + one exception". - [ ] **Option C — Per-path policy hook** — `IAuthPolicy::cspOverride(path)` so consumers can relax CSP for `/swagger/*` etc. without writing a new interceptor. Most flexible but adds another virtual to the policy interface.
uwe.admin added the
evaluated
label 2026-04-25 13:33:15 +02:00
Author
Owner

Evaluated #3 — Small, recommend accept; decision checkbox for strict-only / strict+ctor / per-path hook.

Evaluated #3 — Small, recommend accept; decision checkbox for strict-only / strict+ctor / per-path hook.
u.schuster added the
accepted
label 2026-04-25 21:31:06 +02:00
Author
Owner

Re-evaluation

Re-checking before implementation: the issue is in the accepted queue but the original evaluation listed three options (A — strict only, B — strict + CspOverride ctor, C — per-path policy hook) without a default recommendation, and no checkbox is ticked. The three shapes give meaningfully different long-term API surfaces, so guessing risks rework.

Recommended default if you'd like me to proceed without further input: Option A. Rationale:

  • Mirrors the established pattern: fewo-webapp already overrides the bundled CSP via its own CspInterceptor (the #410 work). Smallest API surface, no virtuals on IAuthPolicy, no struct knob.
  • Per-repo CSP overrides have so far always been more complex than a single string knob (per-path swagger relaxations, per-request nonces) — so Option B's simple knob would soon be outgrown anyway.
  • Option C (per-path hook) is more flexible but adds a virtual to IAuthPolicy, which then ripples to every consumer test fixture.

Please tick a box on the original evaluation comment (or reply with the chosen letter) and I'll proceed.

## Re-evaluation Re-checking before implementation: the issue is in the `accepted` queue but the original evaluation listed three options (A — strict only, B — strict + `CspOverride` ctor, C — per-path policy hook) without a default recommendation, and no checkbox is ticked. The three shapes give meaningfully different long-term API surfaces, so guessing risks rework. **Recommended default if you'd like me to proceed without further input: Option A.** Rationale: - Mirrors the established pattern: fewo-webapp already overrides the bundled CSP via its own `CspInterceptor` (the #410 work). Smallest API surface, no virtuals on `IAuthPolicy`, no struct knob. - Per-repo CSP overrides have so far always been more complex than a single string knob (per-path swagger relaxations, per-request nonces) — so Option B's simple knob would soon be outgrown anyway. - Option C (per-path hook) is more flexible but adds a virtual to `IAuthPolicy`, which then ripples to every consumer test fixture. Please tick a box on the original evaluation comment (or reply with the chosen letter) and I'll proceed.
Author
Owner

Re-evaluated #3 — accepted but decision unticked; recommend Option A, awaiting choice before implementing.

Re-evaluated #3 — accepted but decision unticked; recommend Option A, awaiting choice before implementing.
Author
Owner

Implemented in 0d23124 — strict baseline + CspOverride ctor (Option B). All 3 ctest tests pass. Note: did not tag v0.3.6 yet because owner has #6 (ObjectMapper refactor) in flight in the same working tree; tag will land once that work is committed.

Implemented in 0d23124 — strict baseline + CspOverride ctor (Option B). All 3 ctest tests pass. Note: did not tag v0.3.6 yet because owner has #6 (ObjectMapper refactor) in flight in the same working tree; tag will land once that work is committed.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: uwe.admin/oatpp-authkit#3
No description provided.