You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(consent): break CSP form-action chain with navigation interstitial
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
Copy file name to clipboardExpand all lines: docs/configuration.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -92,7 +92,7 @@ control.
92
92
|`PKCE_REQUIRED`|`true`| Set `false` for legacy clients that omit PKCE (Cursor, MCP Inspector, ChatGPT). Rejected by `PROD_MODE`. |
93
93
|`COMPAT_ALLOW_STATELESS`|`false`| Synthesize a server-side `state` on `/authorize` when the client omits it. Strict mode refuses the request; counter `mcp_auth_access_denied_total{reason="state_missing"}` fires either way. Rejected by `PROD_MODE`. |
94
94
|`RENDER_CONSENT_PAGE`|`true`| Render an explicit proxy-side consent page on `/authorize` so the user sees who's asking and where they'll be redirected before the IdP login. Closes the silent-token-issuance path where a malicious DCR client + an active IdP session = tokens issued without any user interaction. Plain HTML, no JavaScript. Set `false` to fall back to the legacy silent-redirect — only when every caller is non-interactive and known-trusted. |
95
-
| `CSP_FORM_ACTION_EXTRA` | (empty) | Comma-separated additional `scheme://host[:port]` origins appended to the consent page's CSP `form-action` source list, alongside `'self'`, the discovered OIDC authorize endpoint, and the current request's validated `redirect_uri` origin (added per render so already-authenticated upstream sessions whose redirect chain stays in one navigation — `POST /consent` → IdP authorize → `/callback` → client `redirect_uri` — are not blocked by Chromium's form-action enforcement at the final hop; the redirect_uri origin is itself filtered through the same CSP3 host-source check below, with a `consent_csp_redirect_uri_skipped` warn when a registered client's host fails the check). Needed when the IdP redirect chain crosses the authorize host: Entra B2C (`tenant.b2clogin.com`), personal Microsoft accounts (`login.live.com`), federated AD FS (customer host), sovereign clouds (`login.microsoftonline.us` / `login.partner.microsoftonline.cn`). Each entry is validated at startup against the CSP3 §2.4 host-source ABNF (stricter than RFC 3986 reg-name — only `ALPHA`/`DIGIT`/`-`/`.` in hostname labels, or `[IPv6]`); paths, queries, fragments, userinfo, wildcards, and sub-delim host characters (`;`, `,`, `&`, `_`, …) are rejected loud so a misconfigured allowlist cannot silently weaken the emitted header. Scheme and host are ASCII-lower-cased on the way in (per CSP3 §6.7.2.5) so the emitted header is greppable in the form an operator typed. Empty for the common one-host-IdP case. |
95
+
|`CSP_FORM_ACTION_EXTRA`| (empty) |**Deprecated, ignored.**`POST /consent` is now answered with a same-origin navigation interstitial (200 + meta refresh) instead of a 302, which terminates Chromium's form-action redirect-chain enforcement at the proxy — the consent page's `form-action` is `'self'`-only and no IdP / client origin needs to be enumerated. Still parsed and validated so existing deployments keep starting; a `csp_form_action_extra_deprecated` startup warning fires when set. Remove it from your deployment. |
96
96
|`OIDC_ALLOW_INSECURE_HTTP`|`false`| Dev-only escape hatch for cleartext `http://` OIDC issuers (Docker Compose Keycloak demo). Rejected when `PROD_MODE=true`. |
0 commit comments