# h3 has a middleware bypass with one gadget

Host header spoofing pollutes h3's lazily-built event.url, so path-scoped auth middleware matches a different path than the route handler, bypassing internal-only guards.

- Payout: Research
- Severity: high
- Program: h3
- Found by: hibwyli
- Disclosed: 2026-03-18
- CVE: CVE-2026-33131
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-3vj8-jmxq-cgj5
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-3vj8-jmxq-cgj5
- Tags: area:web domain:web impact:internal-access kind:research platform:ghsa sev:high tech:nodejs technique:source-review vuln:access-control vuln:auth-bypass vuln:host-header

**Target:** h3 Node.js HTTP server request URL parsing / middleware routing
**Root cause:** FastURL lazily builds #url from untrusted Host header via href getter; route is resolved before middleware, and middleware path matching uses the attacker-controlled URL, so route and middleware see different paths.
**Steps:**
1. Set up h3 app with `app.use("/internal/run", internalOnly)` guard and a logger accessing `event.url.hostname`.
2. Send request to `/internal/run` with header `Host: localhost:3000/abchehe?`.
3. Accessing `event.url` triggers FastURL `_url` getter, building NativeURL from Host-derived href.
4. Middleware path match resolves to `/abchehe` instead of `/internal/run`, skipping internalOnly.
5. Route handler still resolves `/internal/run` and returns `Internal OK`.
**How found:** Noticed that logging event.url.hostname triggers the FastURL _url getter, traced that #url is null and href is built from untrusted Host header, then audited handler() to see route resolution precedes middleware and uses a different URL view.
**Exploitation:** Attacker sends a request with a crafted Host header containing a path/query suffix; any middleware or logger that touches event.url triggers lazy URL construction from the spoofed host, causing path-scoped middleware to mismatch the actual route and be skipped.
**Insight:** When a framework lazily materializes a URL object from the Host header, any middleware that path-matches on that object can diverge from the router's own path resolution — look for lazy getters on request URL and compare router vs middleware path sources.

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

**Python PoC with spoofed Host header**

```
import requests
url = "http://localhost:3000"
headers = {
    "Host":f"localhost:3000/abchehe?"
}
res = requests.get(f"{url}/internal/run",headers=headers)
print(res.text)
```

