GHSA-72gr-qfp7-vwhw
## Summary The `serveStatic` utility in h3 applies a redundant `decodeURI()` call to the request pathname after `H3Event` has already performed percent-decoding with `%25` preservation. This double decoding converts `%252e%252e` into `%2e%2e`, which bypasses `resolveDotSegments()` (since it checks for literal `.` characters, not percent-encoded equivalents). When the resulting asset ID is resolved by URL-based backends (CDN, S3, object storage), `%2e%2e` is interpreted as `..` per the URL Standard, enabling path traversal to read arbitrary files from the backend. ## Details The vulnerability is a conflict between two decoding stages: **Stage 1 — `H3Event` constructor** (`src/event.ts:65-69`): ```typescript if (url.pathname.includes("%")) { url.pathname = decodeURI( url.pathname.includes("%25") ? url.pathname.replace(/%25/g, "%2525") : url.pathname, ); } ``` This correctly preserves `%25` sequences by escaping them before decoding. A request for `/%252e%252e/etc/passwd` produces `event.url.pathname` = `/%2e%2e/etc/passwd` — the `%25` was preserved so `%252e` became `%2e` (not `.`). **Stage 2 — `serveStatic`** (`src/utils/static.ts:86-88`): ```typescript const originalId = resolveDotSegments( decodeURI(withLeadingSlash(withoutTrailingSlash(event.url.pathname))), ); ``` This applies a **second** `decodeURI()`, which decodes `%2e` → `.`, producing `/../../../etc/passwd`. However, the decoding happens *inside* the `resolveDotSegments()` call argument — `decodeURI` runs first, then `resolveDotSegments` processes the result. Wait — re-examining the flow more carefully: 1. Input pathname after event.ts: `/%2e%2e/%2e%2e/etc/passwd` 2. `decodeURI()` in static.ts converts `%2e` → `.`, producing: `/../../../etc/passwd` 3. `resolveDotSegments("/../../../etc/passwd")` **does** resolve `..` segments, clamping to `/etc/passwd` The actual bypass is subtler. `decodeURI()` does **not** decode `%2e` — it only decodes characters that `encodeURI` would encode. Since
Properties
- ghsa_id
- GHSA-72gr-qfp7-vwhw
- severity
- medium
- summary
- h3: Double Decoding in `serveStatic` Bypasses `resolveDotSegments` Path Traversal Protection via `%252e%252e`
- cvss_score
- 5.9
- cve_id
- GHSA-72gr-qfp7-vwhw
- cvss_vector
- CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N
- is_ghsa_only
- true
- ghsa_published
- 2026-03-20T20:50:09Z
- source_url
- https://github.com/advisories/GHSA-72gr-qfp7-vwhw
- ghsa_updated
- 2026-03-20T20:50:11Z
Related Entities (3)
AFFECTS (1)
HAS_WEAKNESS (1)
REPORTED_BY (1)
Explore deeper with Ninja Signal's threat intelligence graph