fix(web): Origin / Referer CSRF defense on every state-changing POST #291
No reviewers
Labels
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
psa-systems/bunyip!291
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/BUNYIP-259-csrf-synchronizer-token"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
BUNYIP-259: every state-changing POST on bunyip-web now refuses cross-origin form submissions before the handler runs. The audit's full spec is a per-session synchronizer-token middleware plus a hidden form input on every
<form>(~25 templates), which is the right end-state but a real regression risk for the OIDC redirect chain. Ship the smaller fully-defensive step first: anOrigin/Referermiddleware that compares the submission's source host against the request's ownHostheader.What this DOES catch:
Originreliably on cross-site POSTs; the mismatch trips the middleware before the auth handler runs.*.{app_domain}submitting to bunyip-web. CSP's form-action was widened by BUNYIP-249/271 to include those origins, so this is the layer that closes the surface that opening created. The child app's Origin is on the child host, not the BFF host.What this does NOT catch:
Exemptions: the
/oauth2/*family is exempt because the spec gates those POSTs on PKCE + state + nonce + client authentication. Cross-origin OIDC flows are exactly what the spec expects an OP to accept; refusing them here would break every legitimate RP integration. The Stripe webhook lives on bunyip-api, not bunyip-web, so it's not affected.Follow-up: a synchronizer-token middleware on top of this is its own ticket. It's defense in depth on top of the Origin check, not a substitute. The token threading needs every
<form>template to render a hidden input and is heavier than this PR's scope.#BUNYIP-259
BUNYIP-259: every state-changing POST on bunyip-web now refuses cross-origin form submissions before the handler runs. The audit's full spec is a per-session synchronizer-token middleware plus a hidden form input on every `<form>` (~25 templates), which is the right end-state but a real regression risk for the OIDC redirect chain. Ship the smaller fully-defensive step first: an `Origin` / `Referer` middleware that compares the submission's source host against the request's own `Host` header. What this DOES catch: - A cross-origin form on any other host POSTing to bunyip-web. Browsers send `Origin` reliably on cross-site POSTs; the mismatch trips the middleware before the auth handler runs. - An attacker-controlled child app under `*.{app_domain}` submitting to bunyip-web. CSP's form-action was widened by BUNYIP-249/271 to include those origins, so this is the layer that closes the surface that opening created. The child app's Origin is on the child host, not the BFF host. What this does NOT catch: - A same-origin XSS that synthesizes a POST. CSP is the layer that stops that one; this middleware can't tell legit JS from injected JS within the same origin. - A browser that strips both Origin and Referer for some misconfigured reason. The middleware fails closed (403); the user retries from a normal-config browser. The error body names the cause so users understand why a request was refused. Exemptions: the `/oauth2/*` family is exempt because the spec gates those POSTs on PKCE + state + nonce + client authentication. Cross-origin OIDC flows are exactly what the spec expects an OP to accept; refusing them here would break every legitimate RP integration. The Stripe webhook lives on bunyip-api, not bunyip-web, so it's not affected. Follow-up: a synchronizer-token middleware on top of this is its own ticket. It's defense in depth on top of the Origin check, not a substitute. The token threading needs every `<form>` template to render a hidden input and is heavier than this PR's scope. #BUNYIP-259