SRE-727: allow-list proxied Kratos endpoints - #9243
Conversation
The /auth proxy forwarded every path under the prefix to Kratos's public API without filtering on path or method, so everything that port serves was reachable on our own origin, and a Kratos version bump could widen that surface with no code change. Add a default-deny guard in front of the proxy listing the 19 endpoints the frontend uses, matched on path and method against a canonicalised path that rejects percent-escapes, backslashes and traversal segments. Anything else gets a 404 and never reaches Kratos. Denials are counted and logged with method and path, query strings excluded. Hardening follow-up to INC-27.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9243 +/- ##
==========================================
+ Coverage 59.63% 59.64% +0.01%
==========================================
Files 1420 1421 +1
Lines 138767 138805 +38
Branches 6555 6565 +10
==========================================
+ Hits 82753 82791 +38
Misses 54950 54950
Partials 1064 1064
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
PR SummaryHigh Risk Overview Adds
Extensive Vitest coverage for allow/deny cases, canonicalisation, guard behavior, and an Express + real proxy stack proving blocked traffic never hits the stub Kratos server. Reviewed by Cursor Bugbot for commit 017093a. Bugbot is set up for automated code reviews on this repo. Configure here. |
Address review feedback on the Kratos endpoint allow-list. The guard is no longer a separate middleware in the `/auth` chain. It now wraps the proxy, so `index.ts` mounts a single `kratosProxy` and the check sits with the proxy it guards. The check stays ahead of the proxy rather than moving into its `proxyReq` hook. That hook is emitted from the outgoing request's `socket` event, so by the time it runs a connection to Kratos is already open, and it fires on a later tick than the proxy's own `try`/`catch` — a throw there becomes an uncaught exception rather than a 404. Also trim the allow-list comments: drop the file paths and line numbers, which go stale, in favour of a short reason per endpoint.
|
Both addressed in 017093a. 2. Comments. Dropped every file path and line number — each entry now just says why it's needed ( 1. Merging into the proxy. Done, but not via
Wrapping gets the structural win you were after — one moving part in the chain, check adjacent to the proxy — with nothing reaching Kratos. Preflights still work, since Tests: 84 pass. Added six that drive a real Express app and a real Generated by Claude Code |
Requested by Ciaran Morinan · Slack thread
🌟 What is the purpose of this PR?
Before. The Node API proxied everything under
/authstraight through to Kratos's public API, with no filtering on path or method. Every endpoint that port serves was reachable on our own origin — including the native/API-client flow variants that mint bearer session tokens, session enumeration and revocation, the error store, FedCM, and Kratos's health and version endpoints, none of which the frontend uses. Because the rule was "forward the whole prefix", any endpoint a future Kratos release adds would become publicly reachable the moment the image was bumped, with no code change and nothing to review.After. One auditable constant lists the 19 endpoints the frontend actually uses — the login, registration, recovery, verification and settings flows, logout,
/sessions/whoami, and the OIDC callback — each annotated with the frontend call site that needs it. Requests match on path and method, against a canonicalised path that rejects percent-escapes, backslashes and traversal segments outright, so there is no gap between what is checked and what is forwarded. Anything else gets a 404 and never reaches Kratos. Query strings, which carry the flow ids, are untouched. Denials are counted and logged with method and path — never the query string, which carries verification and recovery codes — so an endpoint that should have been listed shows up as a log line rather than a silent auth-flow breakage.This is hardening follow-up to INC-27 (Kratos email verification bypass), and it brings Kratos in line with the Hydra proxy, which is already selectively mounted.
🔗 Related links
🚫 Blocked by
🔍 What does this change?
/authmiddleware chain, last before the proxy — socorshas already answered anyOPTIONSpreflight, and probes for unlisted endpoints are still rate limited.Two entries a reviewer should check specifically:
GET /self-service/logout— Ory'supdateLogoutFlowis a GET despite being the state-changing half, so method-matching it as POST would silently break logout./self-service/methods/oidc/callback/:provider— the only entry with no frontend caller. The IdP redirects the browser there, and Kratos is configured to advertise this proxy as its OIDC redirect base, so denying it would break SSO on return from the provider. POST is allowed alongside GET forform_post-mode providers.Pre-Merge Checklist 🚀
🚢 Has this modified a publishable library?
This PR:
📜 Does this require a change to the docs?
The changes in this PR:
🕸️ Does this require a change to the Turbo Graph?
The changes in this PR:
Behaviour changes worth knowing:
HEADis now denied everywhere, including on/sessions/whoami. No frontend code uses it.[a-z0-9_-]would 404, which is not the case today.🐾 Next steps
🛡 What tests cover this?
cors, verifying that the mount strips/auth, that query strings survive, that theOPTIONSpreflight is answered before the guard, and that unlisted paths and method mismatches never reach the proxy. These were validation-only and are not included in the diff.--report-unused-disable-directives) and formatting all clean, with no new findings on the modifiedindex.tsrelative to its state onmain.❓ How to test this?
/auth(for example one of the/self-service/*/apiflow variants, or/version). Confirm it returns 404 and that a warning naming the method and path appears in the API log.📹 Demo
Not applicable — no user-visible UI change.