mediumCVSS 5.9Vulnerability

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)

[Software]npm/h3

HAS_WEAKNESS (1)

[Weakness]Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

REPORTED_BY (1)

[Source]GitHub Advisory Database

Explore deeper with Ninja Signal's threat intelligence graph

GHSA-72gr-qfp7-vwhw (CVSS 5.9) — Ninja Signal Threat Intelligence | Ninja Signal