feat(csrf): add generic Origin/Referer CSRF guard to dunite-core #36

Merged
nrupard merged 1 commit from feat/DEV-526-csrf-middleware into main 2026-08-06 16:00:51 +02:00
Owner

What

Adds a generic Origin / Referer CSRF guard to dunite-core::middleware (OriginGuard), alongside the existing RequestIdMiddleware and SecurityHeaders. This is the shared home for the guard bunyip-api carries locally; a8n's API side (which today has no API-level CSRF layer) can adopt the same crate with no platform change.

The guard is the bunyip implementation made framework-neutral: the app-specific bits are now constructor parameters, so the crate holds no product strings.

  • origins: &[String] - the allow-list (the same values the CORS layer echoes). Unparseable entries are logged and dropped, matching how the CORS layer tolerates a bad entry.
  • exempt_prefixes: &[&str] - request-path prefixes skipped entirely (endpoints cross-origin by design that carry their own auth: an OAuth/OIDC surface gated by PKCE+state+nonce, an HMAC-signed webhook).
  • ambient_cookies: &[&str] - cookie names that authenticate without proof of intent; a request carrying none has no CSRF surface.

Behaviour

Identical to the source implementation: safe methods pass, exempt prefixes pass, a request with no ambient cookie passes, Origin is checked against the serialized origin allow-list with a Referer host+port fallback, the literal null origin fails closed, and a request with neither header passes (a server-to-server hop, not a browser write). Rejections return 403 with code: CSRF_ORIGIN_REJECTED. All eleven unit tests moved into the crate, parameterized via the new constructor args.

Docs

middleware/mod.rs, lib.rs, dunite-core/Cargo.toml description, and the crates block in CLAUDE.md updated to list the CSRF guard next to request-id / security-headers.

Validation

cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace all green in the pinned rust-builder-glibc:v1.0.1-rust1.94 image.

Consumer follow-up

bunyip-api swaps its bunyip_api::csrf::OriginGuard for dunite_core::middleware::OriginGuard and deletes its copy in a bunyip PR that bumps the dunite pin to this merged rev (DEV-526, tracked under BUNYIP-340).

#DEV-526

## What Adds a generic Origin / Referer CSRF guard to `dunite-core::middleware` (`OriginGuard`), alongside the existing `RequestIdMiddleware` and `SecurityHeaders`. This is the shared home for the guard bunyip-api carries locally; a8n's API side (which today has no API-level CSRF layer) can adopt the same crate with no platform change. The guard is the bunyip implementation made framework-neutral: the app-specific bits are now constructor parameters, so the crate holds no product strings. - `origins: &[String]` - the allow-list (the same values the CORS layer echoes). Unparseable entries are logged and dropped, matching how the CORS layer tolerates a bad entry. - `exempt_prefixes: &[&str]` - request-path prefixes skipped entirely (endpoints cross-origin by design that carry their own auth: an OAuth/OIDC surface gated by PKCE+state+nonce, an HMAC-signed webhook). - `ambient_cookies: &[&str]` - cookie names that authenticate without proof of intent; a request carrying none has no CSRF surface. ## Behaviour Identical to the source implementation: safe methods pass, exempt prefixes pass, a request with no ambient cookie passes, `Origin` is checked against the serialized origin allow-list with a `Referer` host+port fallback, the literal `null` origin fails closed, and a request with neither header passes (a server-to-server hop, not a browser write). Rejections return 403 with `code: CSRF_ORIGIN_REJECTED`. All eleven unit tests moved into the crate, parameterized via the new constructor args. ## Docs `middleware/mod.rs`, `lib.rs`, `dunite-core/Cargo.toml` description, and the crates block in `CLAUDE.md` updated to list the CSRF guard next to request-id / security-headers. ## Validation `cargo fmt --all --check`, `cargo clippy --workspace --all-targets -- -D warnings`, `cargo test --workspace` all green in the pinned `rust-builder-glibc:v1.0.1-rust1.94` image. ## Consumer follow-up bunyip-api swaps its `bunyip_api::csrf::OriginGuard` for `dunite_core::middleware::OriginGuard` and deletes its copy in a bunyip PR that bumps the dunite pin to this merged rev (DEV-526, tracked under BUNYIP-340). #DEV-526
feat(csrf): add generic Origin/Referer CSRF guard to dunite-core
All checks were successful
Check / fmt + clippy + test (pull_request) Successful in 1m27s
create-release / create-release (pull_request) Has been skipped
cac50e9992
Adds OriginGuard to dunite-core::middleware, alongside RequestIdMiddleware and SecurityHeaders. It is the cookie-write CSRF guard bunyip-api carries locally, made framework-neutral: the allow-list origins, the exempt-path prefixes and the ambient-cookie names are constructor parameters, so the crate holds no product strings.

Behaviour matches the source: safe methods pass, exempt prefixes pass, a request with no ambient cookie passes, Origin is checked against the serialized-origin allow-list with a Referer host+port fallback, the literal null origin fails closed, and a request with neither header passes as a non-browser server-to-server hop. Rejections return 403 with code CSRF_ORIGIN_REJECTED. All eleven unit tests move into the crate, parameterized via the new constructor args.

mod.rs, lib.rs, the Cargo.toml description and the CLAUDE.md crates block are updated to list the CSRF guard next to request-id and security-headers.

#DEV-526
nrupard deleted branch feat/DEV-526-csrf-middleware 2026-08-06 16:00:51 +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!36
No description provided.