Skip to content

fix: don't crash the server on URI paths with invalid percent-encoding - #2579

Open
arekborucki wants to merge 2 commits into
mainfrom
fix/malformed-uri-crash
Open

fix: don't crash the server on URI paths with invalid percent-encoding#2579
arekborucki wants to merge 2 commits into
mainfrom
fix/malformed-uri-crash

Conversation

@arekborucki

@arekborucki arekborucki commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

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/%c1 sequences on /chat paths). 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%80 are overlong UTF-8 sequences) — crash the whole server process:

UnhandledPromiseRejection: This error originated either by throwing inside of an async
function without a catch block, or by rejecting a promise which was not handled with
.catch(). The promise rejected with the reason "#<Redirect>".
    at throwUnhandledRejectionsMode (node:internal/process/promises:392:7)

Node's default --unhandled-rejections=throw terminates the process, so any unauthenticated request with such a path takes down the pod.

Root cause

SvelteKit calls the handle hook even when the pathname fails to decode (decode_pathname throws → resolved_path = null), and only produces the 400 Malformed URI error page later, inside resolve(). Our handle hook never gets that far for anonymous requests: the OAuth login branch runs first, and triggerOauthFlow's thrown redirect(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 inside resolve(), just earlier. Malformed paths never reach the OAuth branch, so nothing throws a Redirect outside SvelteKit's control.

(An earlier revision also converted triggerOauthFlow to return a raw 302 Response instead of throwing — reverted per review feedback, since SvelteKit needs the thrown Redirect to 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' → 400 Malformed URI (previously: process crash)
  • Login flow unchanged (OAuth redirect logic untouched)

Note: I haven't run this locally — flagging so reviewers know a smoke test still needs a pass.

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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/lib/server/auth.ts Outdated
// 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 } });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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>
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