Repository navigation
Description
🤖 Generated by the Agentic Engineer
Evidence
All seven oauth2-proxy-gated HTTPRoutes set a fixed, per-host root value for
X-Auth-Request-Redirect:
| Route | Value |
|---|---|
apps/homepage |
https://${domain}/ |
apps/backstage |
https://backstage.${domain}/ |
infrastructure/opencost |
https://opencost.${domain}/ |
infrastructure/policy-reporter |
https://policy-reporter.${domain}/ |
controllers/cilium (hubble) |
https://hubble.${domain}/ |
controllers/coroot |
https://observability.${domain}/ |
controllers/ksail-operator |
https://ksail.${domain}/ |
oauth2-proxy runs with reverse_proxy = true
(controllers/oauth2-proxy/helm-release.yaml:143), and in that mode it gives
X-Auth-Request-Redirect priority over the forwarded-URI headers.
Audience and problem
Anyone following a deep link into one of these UIs while logged out. After
authenticating they land on the service root, not the page they asked for —
the original path and query string are discarded. It affects shared links to a
specific Coroot dashboard, an OpenCost report, a Backstage entity page, or a
KSail cluster view, and any bookmarked deep link after the session cookie expires.
Expected behaviour
After the OIDC round-trip the user arrives at the URI they originally requested,
path and query preserved.
Why this is not a per-route fix
The value is identical in shape across all seven routes, so correcting one leaves
the other six broken and makes that route inconsistent. Gateway API's
RequestHeaderModifier cannot interpolate a dynamic per-request value, so the fix
is necessarily shared: either drop the fixed header and let oauth2-proxy derive the
target from trusted forwarded headers (X-Forwarded-Uri), or supply a dynamic
redirect another way — both of which change shared oauth2-proxy configuration and
need validating against every consumer at once.
Acceptance criteria
- A logged-out request to a deep link (e.g.
https://observability.${domain}/<some-view>?<query>) lands back on that exact URI after authenticating. - Verified on at least two of the seven services, one infrastructure and one app.
- The remaining five behave the same way (no route left on the old fixed-root pattern).
- No regression in the login flow itself, including the CSRF/cookie behaviour documented in the oauth2-proxy HelmRelease comments.
Size
Small-to-medium: the change is concentrated in oauth2-proxy config plus removing
the header from seven routes, but it needs a real end-to-end auth check rather
than static validation.
Notes
Surfaced by a CodeRabbit finding on platform#2723, where the ksail-operator route
adopted the existing pattern. Refuted as out of scope there — the route follows the
established convention, and fixing it alone would not fix the defect.
Related to #1881, which would replace the auth-proxy hop with a Gateway API
ExternalAuth filter and revisits these headers as part of that migration; this
issue is the current-architecture defect and should not wait on it.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Effort
Projects
- StatusShow more project fields✅ Done
Activity
coderabbitai commentedon Aug 16, 2026
🔗 Related PRs
#2728 - fix(apps): restrict public Umami routes [merged]
#2740 - fix(security): restrict tenant gateway hostnames [merged]
#2742 - fix(apps): gate Actual Budget behind oauth2-proxy [merged]
#3109 - fix(deploy): avoid redundant GHCR node reboots [merged]
📝 Issue Planner
Check the box below or use the
@coderabbitai plancommand to generate an implementation plan and prompts that you can use with your favorite coding assistant.🧪 Issue enrichment is currently in open beta.
You can configure auto-planning by selecting labels in the issue_enrichment configuration.
To disable automatic issue enrichment, add the following to your
.coderabbit.yaml:💬 Have feedback or questions? Drop into our discord!
devantler commentedon Sep 25, 2026
Picked up — the scope is wider than the issue records, and the fix is a staged change to where logins finish
Measured live on prod (2026-09-25 ~10:50Z), following a logged-out deep link on four hosts and reading the target oauth2-proxy stores in the login
state:observability.https://observability.<domain>/backstage.https://backstage.<domain>/analytics./websites?view=1(relative)budget./budget?m=2(relative)So there are two defects with one root, and the second is worse than the one this issue describes: the routes that dropped the fixed header (#2742, #3813) keep the path, but every login finishes at the single
oauth2-proxy.<domain>callback, so a relative target resolves against the wrong host.Root cause. oauth2-proxy (v7.15.3, read from its source) completes a login at
redirect_url, and ours is fixed to one central host. A fixed per-route root header hides that for eight routes; the two without it show it.Fix. Let oauth2-proxy finish each login on the host it started on (drop the fixed
redirect_url; it then deriveshttps://<host>/oauth2/callback, forced to https bycookie_secure), after which the relative target is correct on every host and the per-route headers can go. It ships in three steps so no login is ever pointed at a callback Dex does not yet accept:https://<host>/oauth2/callbackon Dex'spublic-clientfor every fronted host, with a guard that derives the hosts from the rendered overlays — no behaviour change.analyticsandbudgetoutright.X-Auth-Request-Redirectheader from the eight routes — deep links survive login everywhere.The open-redirect surface is unchanged:
rdalready takes precedence over the header, and targets are still validated against the samewhitelist_domains.chore: agent-claim #3169 nonce=7c0904fd625e992b37781b88cfa4335f
chore(auth): re-approve the authorization surface for the Dex callbacks
chore(auth): re-approve the authorization surface for per-host logins
devantler commentedon Sep 25, 2026
Verified live on 2026-09-25 at 19:3xZ. The fixes (#4181, #4185, #4191) merged earlier today, but the gateway was not applying route changes until #4199 restarted the Cilium operator (#4198). So the header change in #4191 only reached users at 19:33Z.
Checked read-only: an unauthenticated request for
/deep/link/check?from=now-1hon all 10 SSO-protected hosts now redirects to Dex with that host's own/oauth2/callbackand returns to the exact path and query after login. Those hosts are platform, analytics, backstage, budget, crossview, hubble, ksail, observability, vault and policy-reporter.fix(sso): return users to the page they asked for after logging in (#213