feat(web): make the CSP host allow-list a config input (BUNYIP-503) #504

Merged
nrupard merged 1 commit from feat/BUNYIP-503-csp-config into feat/BUNYIP-501-skin-module 2026-08-07 20:16:54 +02:00
Owner

B5 of the BUNYIP-459 theme-agnostic web-kit epic.

What

Make bunyip-web's CSP host allow-list a config input, so a deploy with different third-party integrations extends the policy without editing security.rs.

Changes

  • Config.csp: CspConfig { connect_src, form_action }, populated from CSP_CONNECT_SRC / CSP_FORM_ACTION (comma-separated). The struct mirrors dunite-core's CspConfig shape.
  • security.rs::policy() appends the skin's hosts to connect-src and form-action only; the script-src 'self' / default-src 'self' / frame-ancestors 'none' lockdown (BUNYIP-424) is never extended by config.
  • bunyip-web/.env.example documents the two variables.

Security

The append is deliberately bounded to connect-src (fetch / XHR / SSE hosts) and form-action (cross-origin form posts, e.g. a payment provider other than Stripe) - the same two directives dunite-core's CspConfig exposes. script-src stays first-party only; a new test asserts a skin's hosts land in connect-src / form-action and never in script-src / default-src / img-src, alongside the existing policy_script_src_is_self_only guard.

Byte-identical by default

An empty CspConfig makes the appender return an empty string, so the policy is byte-identical to before; a test pins the default connect-src / form-action tails. The bunyip-web tests pass.

Note on dunite-core

The spec suggested aligning on dunite-core's CspConfig. bunyip-web is a standalone binary with no dunite-core dependency and its own bespoke security.rs (BUNYIP-232), so I mirrored dunite-core's CspConfig shape locally rather than pulling in the dependency and rewriting the policy string builder (which would risk changing the CSP). B4 (extracting the web-edge modules into the shared web-kit crate) is where this unifies with dunite-core's CspConfig.

Stacked

On B3 part 2 (#503) -> #502 -> #501 -> merged B1. Base = feat/BUNYIP-501-skin-module.

Validation

cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, and cargo test -p bunyip-web are green.

B5 of the BUNYIP-459 theme-agnostic web-kit epic. ## What Make bunyip-web's CSP host allow-list a config input, so a deploy with different third-party integrations extends the policy without editing `security.rs`. ## Changes - `Config.csp: CspConfig { connect_src, form_action }`, populated from `CSP_CONNECT_SRC` / `CSP_FORM_ACTION` (comma-separated). The struct mirrors dunite-core's `CspConfig` shape. - `security.rs::policy()` appends the skin's hosts to `connect-src` and `form-action` only; the `script-src 'self'` / `default-src 'self'` / `frame-ancestors 'none'` lockdown (BUNYIP-424) is never extended by config. - `bunyip-web/.env.example` documents the two variables. ## Security The append is deliberately bounded to `connect-src` (fetch / XHR / SSE hosts) and `form-action` (cross-origin form posts, e.g. a payment provider other than Stripe) - the same two directives dunite-core's `CspConfig` exposes. `script-src` stays first-party only; a new test asserts a skin's hosts land in `connect-src` / `form-action` and never in `script-src` / `default-src` / `img-src`, alongside the existing `policy_script_src_is_self_only` guard. ## Byte-identical by default An empty `CspConfig` makes the appender return an empty string, so the policy is byte-identical to before; a test pins the default `connect-src` / `form-action` tails. The bunyip-web tests pass. ## Note on dunite-core The spec suggested aligning on dunite-core's `CspConfig`. bunyip-web is a standalone binary with no dunite-core dependency and its own bespoke `security.rs` (BUNYIP-232), so I mirrored dunite-core's `CspConfig` shape locally rather than pulling in the dependency and rewriting the policy string builder (which would risk changing the CSP). B4 (extracting the web-edge modules into the shared web-kit crate) is where this unifies with dunite-core's `CspConfig`. ## Stacked On B3 part 2 (#503) -> #502 -> #501 -> merged B1. Base = `feat/BUNYIP-501-skin-module`. ## Validation `cargo fmt --all --check`, `cargo clippy --workspace --all-targets -- -D warnings`, and `cargo test -p bunyip-web` are green.
B5 of the theme-agnostic web-kit epic. A skin with different third-party integrations extends bunyip-web's Content-Security-Policy without editing security.rs.

Config.csp: CspConfig { connect_src, form_action } is populated from CSP_CONNECT_SRC / CSP_FORM_ACTION (comma-separated) and mirrors dunite-core's CspConfig shape. security.rs::policy() appends the skin's hosts to connect-src and form-action only; the script-src 'self' / default-src / frame-ancestors lockdown (BUNYIP-424) is never extended by config. A new test asserts the skin hosts land in connect-src / form-action and never leak into script-src / default-src / img-src, and another pins that an empty config is byte-identical to the previous policy. .env.example documents the two variables.

bunyip-web is a standalone binary with no dunite-core dependency, so the CspConfig shape is mirrored locally rather than pulling in the dependency and rewriting the bespoke policy builder; B4 (the web-kit crate) is where the web-edge security module unifies with dunite-core's CspConfig. Empty config yields the byte-identical default policy; the 213 bunyip-web tests pass. Stacked on #503.

#BUNYIP-503

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard merged commit 00199db8e2 into feat/BUNYIP-501-skin-module 2026-08-07 20:16:54 +02:00
nrupard deleted branch feat/BUNYIP-503-csp-config 2026-08-07 20:16:54 +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/bunyip!504
No description provided.