Skip to content

CVE-2026-86253: Verified Reproduction

CVE-2026-86253: h3 path traversal via unsanitized static file serving pathname

CVE-2026-86253 is verified against h3js/h3 · github. Vulnerability class: Path Traversal. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00363.

REPRO-2026-00363 h3js/h3 · github Path Traversal Sep 24, 2026 CVE entry .txt
Severity
HIGH
Confidence
HIGH
Reproduced in
21m 47s
Tool calls
213
Spend
$5.49
01 · Overview

What Is CVE-2026-86253?

CVE-2026-86253 is a high-severity Path Traversal vulnerability affecting h3js/h3. Pruva has independently reproduced it and publishes a verified, runnable proof-of-concept (reproduction REPRO-2026-00363).

02 · Severity & CVSS

CVE-2026-86253 Severity

CVE-2026-86253 is rated high severity.

HIGH threat level
Weakness CWE-22 — Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

High — serious impact or readily exploitable. Prioritize remediation.

How to Reproduce CVE-2026-86253

$ pruva-verify REPRO-2026-00363
or curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00363/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-86253

Information disclosure — reproduced
  • reached the target end-to-end
  • full exploit chain demonstrated
  • on the real production code path
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

HTTP pathname /%2e%2e/private-sentinel.txt sent with URL normalization disabled

Attack chain
  1. Node HTTP listener
  2. h3 event URL
  3. serveStatic
  4. getMeta/getContents filesystem callbacks
Runnable proof: reproduction_steps.sh
Captured evidence: fixed attempt 1 serverfixed attempt 2 servervulnerable attempt 1 servervulnerable attempt 2 server
How the agent worked 346 events · 213 tool calls · 22 min
22 minDuration
213Tool calls
38Reasoning steps
346Events
22Dead-ends
Agent activity over 22 min
Policy
1
Support
32
Repro
128
Judge
29
Variant
151
Verify
1
0:0021:35

Root Cause and Exploit Chain for CVE-2026-86253

Versions: < 1.15.6 on the 1.x line and >= 2.0.0, <= 2.0.1-rc.14 on the 2.x release-candidate line. Patched versions are 1.15.6 and 2.0.1-rc.15.

CVE-2026-86253 / GHSA-wr4h-v87w-p3r7 is an unauthenticated path-traversal vulnerability in h3's serveStatic() HTTP static-file handler. On the vulnerable Node.js path, an HTTP pathname containing percent-encoded dot segments remains unnormalized until serveStatic() calls decodeURI(). This changes %2e%2e into .. and passes the resulting traversal identifier to the configured getMeta() and getContents() callbacks. When those callbacks implement h3's documented filesystem-serving pattern with path.join(), the resolved path escapes the static root. In this run, h3 1.15.5 disclosed a unique file placed outside the static directory in two independent HTTP attempts, while h3 1.15.6 returned HTTP 404 to the same request in two fixed negative-control attempts.

  • Package/component affected: npm package h3, specifically serveStatic() in src/utils/static.ts on Node.js deployments using the raw request-URL fast path.
  • Affected versions: < 1.15.6 on the 1.x line and >= 2.0.0, <= 2.0.1-rc.14 on the 2.x release-candidate line. Patched versions are 1.15.6 and 2.0.1-rc.15.
  • Tested vulnerable target: Published h3@1.15.5 npm archive, SHA-256 f99c479e7c12d0ad30c01e32fb76415a5eb8bf5f90ac0a036baf14d3f8432a7d.
  • Tested fixed target: Published h3@1.15.6 npm archive, SHA-256 3b47bca4d3eacae8c61a6cc6a1049ec1c91c9a7cd29c710978977260a2dbe1ee.
  • Risk and consequences: A remote unauthenticated requester can read files accessible to the Node service account. Potential targets include application source, .env files, API or database credentials, private keys, and operating-system files. The advisory rates the issue Moderate (CVSS 3.1 score 5.9), while the submitted ticket labels the operational risk High because confidentiality impact can be substantial.

Impact Parity

  • Disclosed/claimed maximum impact: Remote arbitrary-file disclosure (info_leak) through an h3 serveStatic endpoint.
  • Reproduced impact from this run: A real localhost HTTP requester selected /%2e%2e/private-sentinel.txt; vulnerable h3 decoded it to /../private-sentinel.txt, resolved that outside public/, and returned the private sentinel in an HTTP 200 response.
  • Parity: full.
  • Not demonstrated: Files that the service account cannot read remain inaccessible. The proof intentionally used a controlled sentinel rather than collecting real credentials or host-sensitive data. No write, code-execution, or privilege-escalation impact was claimed or tested.

Root Cause

The vulnerable statement in src/utils/static.ts was:

const originalId = decodeURI(withLeadingSlash(withoutTrailingSlash(event.url.pathname)));

On Node.js, h3 receives request pathnames through srvx's optimized URL path, which preserves percent-encoded dot segments rather than applying WHATWG URL dot-segment normalization. Consequently, the attacker input /%2e%2e/private-sentinel.txt reaches serveStatic() in encoded form. decodeURI() then converts %2e%2e into the literal segment .., yielding /../private-sentinel.txt. Vulnerable h3 performs no subsequent traversal normalization before calling the application-supplied static back end. The documented filesystem callbacks join this identifier with the configured public root, and Node's path resolution selects the sibling private file.

Fix commit 0e751b4059060f2ade01a0bdfd96b0f5ffc8a26d added resolveDotSegments() and applied it immediately after decoding. The script resolves the commit's vulnerable parent as 7791538e15ca22437307c06b78fa155bb73632a6, verifies that the parent lacks this call, verifies that the fixed commit contains it, and records the exact diff at bundle/repro/evidence/source_fix.diff. The fix collapses traversal segments without allowing resolution above the static-root identifier.

A distinct later advisory, GHSA-72gr-qfp7-vwhw, concerns a double-decoding bypass through %252e%252e and was fixed in 1.15.9. That follow-up is intentionally left for the required variant-analysis stage and does not change the base CVE result against the originally patched 1.15.6 control.

Reproduction Steps

  1. Run bash bundle/repro/reproduction_steps.sh from any directory. The script derives its bundle path from its own location or PRUVA_ROOT.
  2. The script reads bundle/project_cache_context.json, uses /pruva/project-cache/repo when the prepared cache is available, and otherwise falls back to bundle/artifacts/h3.
  3. It clones or reuses https://github.com/h3js/h3, fetches fix commit 0e751b4059060f2ade01a0bdfd96b0f5ffc8a26d, resolves its parent, and checks the fixing hunk on both sides.
  4. It retrieves and installs the exact published h3 1.15.5 and 1.15.6 archives. The generated Node server uses the real h3 createApp, eventHandler, serveStatic, and toNodeListener code with filesystem callbacks.
  5. For each attempt, it starts a fresh bounded Node HTTP listener, verifies a legitimate health.txt request, then sends the raw pathname /%2e%2e/private-sentinel.txt using curl --path-as-is.
  6. It requires two vulnerable attempts to return HTTP 200 with the out-of-root sentinel and two fixed attempts to return a non-200 response without that sentinel. It exits nonzero on any mismatch.
  7. Before success, it writes bundle/repro/runtime_manifest.json with target/runtime identity and SHA-256 mappings for every finalized proof artifact.

Expected terminal evidence includes:

[+] CONFIRMED vulnerable attempt 1: HTTP 200 disclosed CVE-2026-86253-OUTSIDE-STATIC-ROOT-7e82b84d
[+] CONFIRMED vulnerable attempt 2: HTTP 200 disclosed CVE-2026-86253-OUTSIDE-STATIC-ROOT-7e82b84d
[+] NEGATIVE CONTROL fixed attempt 1: HTTP 404, no sentinel disclosure
[+] NEGATIVE CONTROL fixed attempt 2: HTTP 404, no sentinel disclosure

Evidence

  • Runtime manifest: bundle/repro/runtime_manifest.json is strict JSON and binds 22 immutable artifacts by SHA-256. It records entrypoint_kind=endpoint, service_started=true, healthcheck_passed=true, and target_path_reached=true.

  • Attacker request: bundle/repro/evidence/vulnerable_attempt_1_request.txt records GET /%2e%2e/private-sentinel.txt HTTP/1.1 across the actual HTTP socket.

  • Vulnerable server path: bundle/repro/evidence/vulnerable_attempt_1_server.log records:

    GET_META id="/../private-sentinel.txt" resolved="/pruva/project-cache/package/h3-cve-2026-86253/runtime/private-sentinel.txt"
    GET_CONTENTS id="/../private-sentinel.txt" resolved="/pruva/project-cache/package/h3-cve-2026-86253/runtime/private-sentinel.txt"
    

    The configured static root is the sibling runtime/public directory, so this proves the selected file escaped that root.

  • Vulnerable response: bundle/repro/evidence/vulnerable_attempt_1_response_headers.txt records HTTP/1.1 200 OK; bundle/repro/evidence/vulnerable_attempt_1_response_body.txt contains the exact outside-root sentinel CVE-2026-86253-OUTSIDE-STATIC-ROOT-7e82b84d.

  • Repeated vulnerable proof: The independently started second endpoint has equivalent evidence in bundle/repro/evidence/vulnerable_attempt_2_*.

  • Fixed negative control: bundle/repro/evidence/fixed_attempt_1_response_headers.txt records HTTP/1.1 404 Not Found, its body does not contain the sentinel, and bundle/repro/evidence/fixed_attempt_1_server.log contains no traversal identifier or private-file callback. Attempt 2 independently repeats this result in bundle/repro/evidence/fixed_attempt_2_*.

  • Source and target identity: bundle/repro/evidence/source_fix.diff records the exact vendor fix. bundle/repro/evidence/target_identity.txt records package digests, source commits, Node v24.18.0, Node executable SHA-256 41a74efb34cbde5c7632cdac0cf8bd1a14d0b8d73dc1e82755014d9a9ce70f5c, Linux, and x86_64.

  • Diagnostic log: bundle/logs/reproduction_steps.log records the complete most recent script execution but is deliberately not hashed while an active tee is writing it.

Recommendations / Next Steps

  • Upgrade the 1.x package line to at least h3 1.15.9, not merely 1.15.6, so both the original CVE and the later double-decoding bypass are addressed. On the 2.x line, use a release that includes both hardening changes rather than a pre-fix release candidate.
  • Canonicalize the decoded URL path exactly once before any static-backend callback and reject or collapse . and .. segments before filesystem or URL resolution.
  • Treat callback implementations as untrusted with respect to containment: resolve the candidate path to an absolute path and prove it lies beneath the configured root with separator-aware logic before opening it.
  • Add regression coverage for lowercase/uppercase percent-encoded dots, single- and double-encoded traversal, encoded separators, mixed separators, repeated slashes, root underflow, query strings, prefixes resembling the root, and non-filesystem static back ends.
  • Retain a fixed-version HTTP negative control in integration tests; unit testing only resolveDotSegments() would not cover the raw Node request-URL boundary and decoding order.

Additional Notes

  • Idempotency: The final script passed twice consecutively in this run. Each script invocation itself starts two clean vulnerable and two clean fixed listeners; thus the final evidence represents four independent service processes per invocation.
  • Real boundary: This is not a direct call to serveStatic(). The attacker-controlled bytes cross a real Node TCP/HTTP listener and flow through h3's request-event and static-serving path before filesystem access.
  • Sanitizers: None were used or needed; information disclosure was directly visible in the HTTP response.
  • Limitations: The proof targets Linux/Node and the vulnerable 1.x line. The advisory notes that runtimes supplying an already normalized WHATWG URL may not follow the vulnerable Node fast path. The exact follow-up %252e%252e bypass belongs to variant analysis.

CVE-2026-86253 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:003:15
0:00
session startedgpt-5.6-sol · CVE-2026-86253 · REPRO-20
0:06
0:07
web search
0:09
web search
0:15
0:16
0:26
0:29
0:40
web search
0:41
web search
0:42
web search
0:45
1:01
web search
1:02
web search
1:03
web search
1:07
1:39
1:39
1:39
1:39
1:41
1:42
1:43
2:53
2:53
extract_facts
no facts extracted
2:55
2:55
supportclaim_contract
3:05
3:05
3:15

Artifacts and Evidence for CVE-2026-86253

Scripts, logs, diffs, and output captured during the reproduction.

08 · How to Fix

How to Fix CVE-2026-86253

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-86253 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-86253

Is CVE-2026-86253 exploitable?

Yes. Pruva independently reproduced CVE-2026-86253 in h3js/h3 and verified the exploit fires end-to-end in a sandboxed environment. A runnable proof-of-concept script and the full agent transcript are on this page (reproduction REPRO-2026-00363).

How severe is CVE-2026-86253?

CVE-2026-86253 is rated high severity.

What type of vulnerability is CVE-2026-86253?

CVE-2026-86253 is classified as CWE-22 (Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')), a Path Traversal vulnerability.

How can I reproduce CVE-2026-86253?

Pruva provides a verified reproduction script on this page. Download it and run it inside an isolated environment such as a container or virtual machine — never against production. The reproduction was confirmed end-to-end by Pruva's automated agents.

Is the CVE-2026-86253 reproduction verified?

Yes. Pruva reproduced CVE-2026-86253 with high confidence in a sandboxed environment, capturing the full agent transcript and artifacts as evidence.
11 · References

References for CVE-2026-86253

Authoritative sources for CVE-2026-86253 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.