Skip to content

fix(helm): remove wildcard redirect URIs - #676

Open
vincentchalamon wants to merge 1 commit into
4.3from
fix/keycloak-wildcard-redirect-uris
Open

fix(helm): remove wildcard redirect URIs#676
vincentchalamon wants to merge 1 commit into
4.3from
fix/keycloak-wildcard-redirect-uris

Conversation

@vincentchalamon

@vincentchalamon vincentchalamon commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes the private advisory GHSA-2rmv-4743-38c6 (High, CVSS 8.1, CWE-601).

realm-demo.json shipped redirectUris: ["*"] and webOrigins: ["*"] on api-platform-pwa (confidential, BFF, standard flow) and on api-platform-swagger — a public client, so no secret is even needed to exchange a stolen code. A victim completing the login flow had a live authorization code delivered to an attacker-controlled origin, exchangeable for a real access_token/id_token.

The Keycloak upgrade merged in #679 does not close this. Measured against the deployed pr-679 environment, running the upgraded Keycloak 26.6.4:

$ curl -o /dev/null -w '%{http_code}' \
    'https://pr-679-demo.api-platform.com/oidc/realms/demo/protocol/openid-connect/auth?client_id=api-platform-pwa&redirect_uri=https://attacker.evil.example/callback&response_type=code&scope=openid'
200

while the properly scoped built-in account client answers 400 Invalid parameter: redirect_uri on the same server. Redirect validation is active; the wildcard is what bypasses it. This PR is the actual remediation.

Changes

Every api-platform-* client now lists explicit redirect URIs and web origins:

client redirect URIs notes
api-platform-pwa the better-auth callback only PKCE S256 enforced, explicit post.logout.redirect.uris
api-platform-swagger the Swagger UI oauth2-redirect.html asset only PKCE S256 enforced
api-platform-api none, standardFlowEnabled: false resource server, never runs a browser flow

post.logout.redirect.uris is required, not decorative: without it Keycloak falls back to the redirect URIs, and /api/auth/keycloak-logout redirects to /books, which would then be rejected. The ## separator is Keycloak's Constants.CFG_DELIMITER.

PKCE enforcement is safe on both: better-auth already sends code_challenge_method=S256 (@better-auth/core/dist/oauth2/create-authorization-url.mjs), and api_platform.oauth.pkce is already enabled while Swagger UI only implements S256.

Dynamic host

Keycloak accepts no wildcard in the host part of a redirect URI, while the deployed host is dynamic (pr-<N>-demo.api-platform.com). The realm ships the app-host.invalid placeholder and the Helm ConfigMap swaps it for the release host, reusing the (first .Values.ingress.hosts).host that already drives oidc-server-url in the same ConfigMap — so deploy.yml needs no change. .invalid is reserved by RFC 2606, so the entry left over in local development is unresolvable and cannot leak a code.

localhost is kept on purpose: e2e/playwright.config.js uses baseURL: 'https://localhost' and the E2E job runs against it. api-platform.github.io is left exactly where it is — the cors list of deploy.yml, feeding php.corsAllowOrigin and mercure.extraDirectives, both untouched. No OIDC flow originates from that origin, so it gets no Keycloak entry; adding https://api-platform.github.io to the Swagger client's webOrigins is a one-liner if the Hydra console ever needs a token.

Points worth discussing

  • api-platform-api loses standardFlowEnabled. It only serves as the uma-ticket audience and as OIDC_AUD; no browser flow uses it. If anything regresses, restoring standardFlowEnabled: true while keeping redirectUris: [] is enough.
  • The Swagger redirect URI hardcodes an API Platform asset path (/bundles/apiplatform/swagger-ui/oauth2-redirect.html, from SwaggerUi/index.html.twig). If a future upgrade moves that asset, Authorize on /docs breaks silently — worth adding to the upgrade checklist.
  • app-host.invalid vs templating the realm. The alternatives were tpl on the whole file or keycloak-config-cli variable substitution; the placeholder keeps realm-demo.json valid JSON for the raw --import-realm that compose uses, which the other options do not.

Verification

Exploit closed — the negative control of the advisory PoC fails for all three clients:

api-platform-pwa         -> 400  Invalid parameter: redirect_uri
api-platform-swagger     -> 400  Invalid parameter: redirect_uri
api-platform-api         -> 400  Invalid parameter: redirect_uri

A legitimate redirect URI is accepted, and PKCE enforcement is observable: omitting code_challenge_method yields error=invalid_request&error_description=Missing+parameter:+code_challenge_method.

Suites (realm reimported from scratch on Keycloak 26.6.4, @read and @write each on a fresh stack as CI does):

  • Playwright @read: 24 passed / 2 flaky · @write: 8 passed / 3 flaky / 0 failed. Same flaky set as before — login-page timing, green on retry.
  • PHPUnit 126 tests / 576 assertions OK · PHPStan · Rector · doctrine:schema:validate · redocly lint · php-cs-fixer all clean.
  • helm lint, and helm template --set ingress.hosts[0].host=pr-676-demo.api-platform.com leaves no app-host.invalid while rendering both the PWA callback and the Swagger UI redirect for that host.

Follow-up

Adding the deploy label exercises the dynamic-host substitution on a real pr-<N>-demo.api-platform.com before merging.

Base automatically changed from fix/pnpm-lockfile to 4.3 July 30, 2026 13:19
@vincentchalamon
vincentchalamon force-pushed the fix/keycloak-wildcard-redirect-uris branch 2 times, most recently from d19cd22 to d19a5ec Compare July 30, 2026 14:02
@vincentchalamon vincentchalamon changed the title fix(helm): remove wildcard redirect URIs and upgrade Keycloak fix(helm): remove wildcard redirect URIs Jul 30, 2026
@vincentchalamon
vincentchalamon changed the base branch from 4.3 to fix/keycloak-upgrade July 30, 2026 14:03
@vincentchalamon
vincentchalamon force-pushed the fix/keycloak-wildcard-redirect-uris branch 3 times, most recently from a6b093b to cb44614 Compare July 30, 2026 17:53
Base automatically changed from fix/keycloak-upgrade to 4.3 July 30, 2026 18:47
GHSA-2rmv-4743-38c6: the `api-platform-pwa` and `api-platform-swagger` clients
were shipped with `redirectUris: ["*"]` and `webOrigins: ["*"]`, so an attacker
could have a live authorization code delivered to any origin.

Every `api-platform-*` client now lists explicit redirect URIs and web origins.
`api-platform-pwa` keeps only the better-auth callback, `api-platform-swagger`
only the Swagger UI oauth2-redirect asset, and `api-platform-api` — a resource
server that never runs a browser flow — none at all. Both user-facing clients
enforce PKCE S256, and `api-platform-pwa` declares its post-logout redirect URIs
explicitly, since Keycloak otherwise falls back to the redirect URIs and would
reject the logout route.

Keycloak accepts no wildcard in the host part of a redirect URI while the
deployed host is dynamic (pr-<N>-demo.api-platform.com), so the realm ships the
unresolvable `app-host.invalid` placeholder and the Helm ConfigMap swaps it for
the host of the release. `.invalid` is reserved by RFC 2606, so the entry left
over in local development cannot leak a code anywhere.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vincentchalamon
vincentchalamon force-pushed the fix/keycloak-wildcard-redirect-uris branch from cb44614 to 454b1ae Compare July 31, 2026 08:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant