fix(consent): widen CSP form-action to the client redirect_uri#35
Merged
Conversation
When the upstream IdP session is already live, the consent POST's redirect chain stays in one navigation (POST /consent → IdP authorize → /callback → client redirect_uri) and Chromium enforces form-action across every hop. Without the client's redirect_uri origin in form-action, Chrome blocks the final hop. Append it per render, filtered through the CSP3 host-source check so a DCR-registered redirect_uri whose host smuggles a sub-delim cannot break out of the directive.
babs
added a commit
that referenced
this pull request
Jun 4, 2026
Chromium enforces the consent page's form-action directive against every hop of the redirect chain a form submit initiates. Client-side hops after redirect_uri (e.g. Power Platform's global.consent.azure-apim.net -> regional UI origin) are unknowable in advance, so origin enumeration (#33 IdP extras, #35 redirect_uri) could never be complete. - answer POST /consent (approve/deny/server-error) with a 200 same-origin interstitial (meta refresh, no JS) instead of a 302; form navigation ends at the proxy, downstream hops are a regular navigation that form-action does not govern - tighten consent page CSP to form-action 'self'; drop buildConsentCSPSources/formatConsentCSP and per-render widening - deprecate CSP_FORM_ACTION_EXTRA (parsed, ignored, startup warn) - re-render consent page with fresh JTI on replayed submit instead of a dead-end 400 consent_replay; decision still requires a new explicit click, single-use guarantee unchanged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Follow-up to #33 (which added the upstream IdP origin to the consent page's
form-action). This adds the client's validatedredirect_uriorigin toform-actionper render.Why
When the user already has a live session with the upstream IdP, the consent POST's redirect chain doesn't terminate at an IdP login page — it stays in one navigation:
Chromium enforces
form-actionacross the entire chain, so without the client'sredirect_uriorigin in the directive Chrome blocks the final hop (surfacing a misleading "violates form-action" error naming/consent).How
buildConsentCSPsplit intobuildConsentCSPSources(static list computed once at startup:'self', IdP origin,CSP_FORM_ACTION_EXTRA) +formatConsentCSP(per-render, appends the request'sredirect_uriorigin).redirect_uriorigin is filtered through the same CSP3 §2.4 host-source check as operator extras (config.CanonicalCSPHostSource, now exported) — DCR validation accepts hosts with sub-delims (;,,, …) that would break out of the directive, so a malicious DCR client cannot weaken its own consent page's CSP. On failure the page renders with the unwidened CSP and aconsent_csp_redirect_uri_skippedwarn (carryingclient_id+redirect_uri) fires.slices.Concat(fresh allocation) rather thanappendinto the shared startup slice — safe under concurrent renders.Docs (
configuration.md,threat-model.md) updated to match.