fix: don't crash the server on URI paths with invalid percent-encoding - #2579
fix: don't crash the server on URI paths with invalid percent-encoding#2579arekborucki wants to merge 2 commits into
Conversation
Scanner requests like /chat/..%c0%af..%c0%afetc/passwd or /chat/WEB-INF/web.xml%C0%80.jsp killed prod pods: SvelteKit calls the handle hook even when the pathname fails to decode (it only produces the 400 Malformed URI error inside resolve()), so the OAuth login branch ran first and its thrown redirect() ended up as an unhandled promise rejection, which terminates the Node process. Two changes: - handle hook rejects paths with invalid percent-encoding with a plain 400 before any auth/OAuth logic - triggerOauthFlow returns a real 302 Response instead of throwing a Redirect, so it can never surface as an unhandled rejection Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a489e7d67c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // function is called from the handle hook, where a thrown Redirect that | ||
| // crosses an unawaited promise boundary becomes an unhandled rejection | ||
| // and kills the Node process (observed in prod with scanner requests). | ||
| return new Response(null, { status: 302, headers: { location: authorizationUrl } }); |
There was a problem hiding this comment.
Preserve SvelteKit redirects for client-side OAuth navigation
When an unauthenticated user reaches a protected page through client-side navigation (including after a session expires), SvelteKit requests page data with fetch. A thrown framework Redirect is converted into SvelteKit's data-redirect payload, whereas this raw 302 bypasses that conversion; the fetch instead attempts to follow the external identity-provider URL and typically fails CORS rather than navigating the browser. Keep the framework redirect for valid requests—the new malformed-path guard already prevents the reported scanner crash—or otherwise preserve SvelteKit's data-request redirect handling.
Useful? React with 👍 / 👎.
data-request redirect handling on client-side navigation (review feedback); the malformed-path guard in the handle hook is the actual crash fix Co-authored-by: Cursor <cursoragent@cursor.com>
Incident
This caused a production DoS incident on HuggingChat (Sep 12). Random internet scanners sending path-traversal probes were repeatedly crashing chat-ui prod pods — each malformed request killed a pod, dropping all in-flight requests. Anyone could take down all replicas with a simple curl loop, no authentication needed. As an emergency mitigation we had to block these requests at the WAF/CDN edge (rule blocking
%c0/%c1sequences on/chatpaths). This PR is the proper application-level fix.Problem
Requests with invalid percent-encoding in the path — e.g.
/chat/..%c0%af..%c0%af..etc/passwd/...or/chat/WEB-INF/web.xml%C0%80.jsp(typical path-traversal scanner probes,%c0%af/%c0%80are overlong UTF-8 sequences) — crash the whole server process:Node's default
--unhandled-rejections=throwterminates the process, so any unauthenticated request with such a path takes down the pod.Root cause
SvelteKit calls the
handlehook even when the pathname fails to decode (decode_pathnamethrows →resolved_path = null), and only produces the400 Malformed URIerror page later, insideresolve(). Our handle hook never gets that far for anonymous requests: the OAuth login branch runs first, andtriggerOauthFlow's thrownredirect(302)ends up crossing an unawaited promise boundary and becomes an unhandled rejection instead of a 302.Fix
Early validation in the handle hook: if
decodeURIComponent(event.url.pathname)throws, return a plain 400 before any auth/OAuth logic — mirroring what SvelteKit itself does insideresolve(), just earlier. Malformed paths never reach the OAuth branch, so nothing throws aRedirectoutside SvelteKit's control.(An earlier revision also converted
triggerOauthFlowto return a raw 302Responseinstead of throwing — reverted per review feedback, since SvelteKit needs the thrownRedirectto build its data-request redirect payload for client-side navigation.)Expected behavior after the fix (please verify in review/CI)
curl 'http://localhost:5173/chat/WEB-INF/web.xml%C0%80.jsp'→ 400Malformed URI(previously: process crash)Note: I haven't run this locally — flagging so reviewers know a smoke test still needs a pass.