feat(security-headers): make CSP connect-src/form-action configurable (BUNYIP-244) #19

Merged
vas2000-work merged 2 commits from feat/BUNYIP-244-configurable-csp into main 2026-06-30 05:28:27 +02:00
Owner

What

Make the dunite-core SecurityHeaders CSP configurable instead of emitting a single hardcoded policy, so a consumer can allowlist the cross-origin destinations its flows legitimately need (an OAuth client redirect_uri origin, a public API origin) on connect-src / form-action without forking the policy or naming any app in the generic crate. Closes the dunite half of BUNYIP-244.

Changes

  • Add CspConfig { connect_src, form_action } and SecurityHeaders::with_csp(cfg) (plus ::new() / Default). The CSP header is built once at construction; supplied origins append only to connect-src / form-action, every other directive stays locked down.
  • SecurityHeaders becomes a struct carrying the precomputed HeaderValue. Existing .wrap(SecurityHeaders) call sites must move to SecurityHeaders::new() (done in the bunyip PR).
  • Fail closed: an empty config reproduces the previous static policy; an origin that would yield an invalid header value falls back to the locked-down default (a misconfigured origin can never widen or corrupt the policy).
  • The crate stays app-agnostic: origins arrive as data, no app or brand is named.

Coordination: overlaps BUNYIP-235

The open fix/BUNYIP-235-csp-form-action-stripe branch edits the SAME directive, adding checkout.stripe.com / billing.stripe.com to form-action (the subscribe / billing-portal flows 302 to those, which CSP3 applies form-action to). To avoid regressing it regardless of merge order, this PR folds those Stripe origins into the configurable base, so the default policy keeps them and consumer origins append on top. Whichever of the two merges second resolves a one-line conflict on that directive (both want Stripe in the base; this PR additionally makes it appendable).

Verification

cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, and cargo test --workspace all green in the rust-builder-glibc:v1.0.1-rust1.94-trixie container (84 tests pass, incl. new coverage for append, fail-closed, and the Stripe-default form-action).

## What Make the `dunite-core` `SecurityHeaders` CSP configurable instead of emitting a single hardcoded policy, so a consumer can allowlist the cross-origin destinations its flows legitimately need (an OAuth client `redirect_uri` origin, a public API origin) on `connect-src` / `form-action` without forking the policy or naming any app in the generic crate. Closes the dunite half of BUNYIP-244. ## Changes - Add `CspConfig { connect_src, form_action }` and `SecurityHeaders::with_csp(cfg)` (plus `::new()` / `Default`). The CSP header is built once at construction; supplied origins append only to `connect-src` / `form-action`, every other directive stays locked down. - `SecurityHeaders` becomes a struct carrying the precomputed `HeaderValue`. Existing `.wrap(SecurityHeaders)` call sites must move to `SecurityHeaders::new()` (done in the bunyip PR). - Fail closed: an empty config reproduces the previous static policy; an origin that would yield an invalid header value falls back to the locked-down default (a misconfigured origin can never widen or corrupt the policy). - The crate stays app-agnostic: origins arrive as data, no app or brand is named. ## Coordination: overlaps BUNYIP-235 The open `fix/BUNYIP-235-csp-form-action-stripe` branch edits the SAME directive, adding `checkout.stripe.com` / `billing.stripe.com` to `form-action` (the subscribe / billing-portal flows 302 to those, which CSP3 applies `form-action` to). To avoid regressing it regardless of merge order, this PR folds those Stripe origins into the configurable base, so the default policy keeps them and consumer origins append on top. Whichever of the two merges second resolves a one-line conflict on that directive (both want Stripe in the base; this PR additionally makes it appendable). ## Verification `cargo fmt --all --check`, `cargo clippy --workspace --all-targets -- -D warnings`, and `cargo test --workspace` all green in the `rust-builder-glibc:v1.0.1-rust1.94-trixie` container (84 tests pass, incl. new coverage for append, fail-closed, and the Stripe-default form-action).
The SecurityHeaders middleware emitted a single hardcoded, Stripe-only Content-Security-Policy. A consuming app whose flow legitimately reaches a cross-origin destination (an OAuth client redirect_uri origin, a public API origin distinct from the serving origin) had no way to allowlist it without forking the policy string, and the blocked request surfaced as a CSP violation that looks like an outage.

Add CspConfig { connect_src, form_action } and SecurityHeaders::with_csp(cfg). The middleware now builds the CSP header once at construction, appending the supplied origins only to connect-src / form-action; every other directive stays locked down. An empty config (SecurityHeaders::new / ::default) reproduces the previous static policy byte-for-byte, and an origin that would yield an invalid header value fails closed to the locked-down default. The crate stays app-agnostic: origins arrive as data, no app or brand is named.

#BUNYIP-244
feat(security-headers): keep Stripe form-action targets in the CSP base
All checks were successful
Checks / fmt + clippy + test (pull_request) Successful in 44s
create-release / create-release (pull_request) Has been skipped
b2d95b5e32
The in-flight BUNYIP-235 branch adds checkout.stripe.com / billing.stripe.com to form-action because the subscribe and billing-portal flows POST same-origin and the handler 302s to those Stripe-hosted destinations, which CSP3 applies form-action to. Fold those origins into the configurable base here so making form-action appendable does not regress that fix regardless of which branch merges first: the default policy still carries the Stripe redirect targets, and consumer-supplied origins append on top of them.

#BUNYIP-235
#BUNYIP-244
vas2000-work deleted branch feat/BUNYIP-244-configurable-csp 2026-06-30 05:28:27 +02:00
Sign in to join this conversation.
No reviewers
No labels
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
psa-systems/dunite!19
No description provided.