CVE-2026-27730: Github.com/esm dev/esm.sh Esm.sh Has SSRF Localhost/private-network Bypass Vulnerability
HIGHCVE-2026-27730 is a HIGH (CVSS 8.6) esm.sh has ssrf localhost/private-network bypass vulnerability (CWE-918: Server-Side Request Forgery (SSRF)) affecting Github.com/esm dev/esm.sh libraries in the Go ecosystem. The issue involves esm.sh has ssrf localhost/private-network bypass in `/http(s)` module route. Remediation is available by upgrading affected packages to version 0.0.0-20250616164159-0593516c4cfa.
CVE-2026-27730 at a Glance
Security Impact
CVSS: 8.6
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N).
How to Fix CVE-2026-27730
To remediate this vulnerability, upgrade all affected Github.com/esm dev/esm.sh dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
Upgrade github.com/esm-dev/esm.sh to 0.0.0-20250616164159-0593516c4cfa
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
inspect dependencies for github.com/esm-dev/esm.sh
How to Check if You Are Affected
To determine whether your application is affected by CVE-2026-27730, inspect your project dependencies for Github.com/esm dev/esm.sh:
inspect dependencies for github.com/esm-dev/esm.sh
Analyze the terminal output to locate any version of Github.com/esm dev/esm.sh in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.0.0-20250616164159-0593516c4cfa), your application is affected.
Note: Verify declared build manifests and deployed runtime container environments.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-918: Server-Side Request Forgery (SSRF), where the web server receives a url or similar request from an upstream component and retrieves the contents of this url, but it does not sufficiently ensure that the request is being sent to the expected destination. In Github.com/esm dev/esm.sh, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-27730 Affected Github.com/esm dev/esm.sh Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
github.com/esm-dev/esm.sh
|
Go |
< 0.0.0-20250616164159-0593516c4cfa
|
0.0.0-20250616164159-0593516c4cfa
|
References
Frequently Asked Questions
What is CVE-2026-27730?
Which Github.com/esm dev/esm.sh versions are affected?
What version fixes CVE-2026-27730?
Is github.com/esm-dev/esm.sh affected?
What is the CVSS score of CVE-2026-27730?
What CWE is associated with CVE-2026-27730?
How can I check Go dependencies for CVE-2026-27730?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-p2v6-84h2-5x4r",
"modified" : "2026-03-02T16:40:47Z",
"published" : "2026-02-25T22:57:59Z",
"aliases" : [ "CVE-2026-27730" ],
"summary" : "esm.sh has SSRF localhost/private-network bypass in `/http(s)` module route",
"details" : "### Summary\nAn SSRF vulnerability (CWE-918) exists in esm.sh’s `/http(s)` fetch route. \nThe service tries to block localhost/internal targets, but the validation is based on hostname string checks and can be bypassed using DNS alias domains (for example, `127.0.0.1.nip.io` resolving to `127.0.0.1`). \nThis allows an external requester to make the esm.sh server fetch internal localhost services. \nSeverity: High (depending on deployment network exposure).\n\n### Details\nThe vulnerable flow starts at the route handling user-controlled remote URLs:\n\n- `server/router.go:532`\n - Accepts paths beginning with `/http://` or `/https://`.\n ```go\nif strings.HasPrefix(pathname, \"/http://\") || strings.HasPrefix(pathname, \"/https://\") {\n\tquery := ctx.Query()\n\tmodUrl, err := url.Parse(pathname[1:])\n\tif err != nil {\n\t\tctx.SetHeader(\"Cache-Control\", ccImmutable)\n\t\treturn rex.Status(400, \"Invalid URL\")\n\t}\n\tif modUrl.Scheme != \"http\" && modUrl.Scheme != \"https\" {\n\t\tctx.SetHeader(\"Cache-Control\", ccImmutable)\n\t\treturn rex.Status(400, \"Invalid URL\")\n\t}\n\tmodUrlStr := modUrl.String()\n\n\t// disallow localhost or ip address for production\n\tif !DEBUG {\n\t\thostname := modUrl.Hostname()\n\t\tif isLocalhost(hostname) || !valid.IsDomain(hostname) || modUrl.Host == ctx.R.Host {\n\t\t\tctx.SetHeader(\"Cache-Control\", ccImmutable)\n\t\t\treturn rex.Status(400, \"Invalid URL\")\n\t\t}\n\t}\n```\n\nThe internal-target block is string-based:\n\n- `server/router.go:545`\n ```go\n\t\t\t// disallow localhost or ip address for production\n\t\t\tif !DEBUG {\n\t\t\t\thostname := modUrl.Hostname()\n\t\t\t\tif isLocalhost(hostname) || !valid.IsDomain(hostname) || modUrl.Host == ctx.R.Host {\n\t\t\t\t\tctx.SetHeader(\"Cache-Control\", ccImmutable)\n\t\t\t\t\treturn rex.Status(400, \"Invalid URL\")\n\t\t\t\t}\n\t\t\t}\n```\n\nLocalhost detection itself is limited to hostname patterns:\n\n- `server/utils.go:72`\n - `isLocalhost(...)` checks values like `localhost`, `127.0.0.1`, and `192.168.*`.\n - It does **not** validate the resolved destination IP after DNS resolution.\n```go\nfunc isLocalhost(hostname string) bool {\n\treturn hostname == \"localhost\" || strings.HasSuffix(hostname, \".localhost\") || hostname == \"127.0.0.1\" || (valid.IsIPv4(hostname) && strings.HasPrefix(hostname, \"192.168.\"))\n}\n```\n\nFetch proceeds with host-string allowlisting:\n\n- `server/router.go:595-596`\n - `allowedHosts[modUrl.Host] = struct{}{}` then `fetch.NewClient(...allowedHosts)`\n```go\nallowedHosts := map[string]struct{}{}\nallowedHosts[modUrl.Host] = struct{}{}\nfetchClient, recycle := fetch.NewClient(ctx.UserAgent(), 15, false, allowedHosts)\ndefer recycle()\n```\n\n- `internal/fetch/fetch.go:49`\n - Host allowlist compares host strings, not resolved IP class.\n```go\nfunc (c *FetchClient) Fetch(url *url.URL, header http.Header) (resp *http.Response, err error) {\n\tif c.allowedHosts != nil {\n\t\tif _, ok := c.allowedHosts[url.Host]; !ok {\n\t\t\treturn nil, errors.New(\"host not allowed: \" + url.Host)\n\t\t}\n\t}\n\tif c.userAgent != \"\" {\n\t\tif header == nil {\n\t\t\theader = make(http.Header)\n\t\t}\n\t\theader.Set(\"User-Agent\", c.userAgent)\n\t}\n\t// ...\n\treturn c.Do(req)\n}\n```\n\nBecause validation is based on host strings and not on resolved destination IP ranges, domains that resolve to loopback/private IP can bypass protections.\n\n### PoC\nReproduction tested on local Docker deployment.\n\n1. Run esm.sh:\n```bash\ndocker run -d --name esmsh-5558 -p 5558:80 ghcr.io/esm-dev/esm.sh:latest\n```\n\n2. Run an internal localhost-only test service (`secret` response) in the same network namespace:\n- Internal network test server code (app.py):\n```python\nfrom flask import Flask, Response\n\n@app.get('/secret.js')\ndef secret_js():\n return Response('secret;\\n', mimetype='application/javascript')\n\nif __name__ == '__main__':\n app.run(host='0.0.0.0', port=5555)\n```\n\nRun the internal Python server container (same network namespace as `esmsh-5558`):\n```bash\ndocker run -d --name internal-5555 --network container:esmsh-5558 \\\n -v \"<YOUR_PATH>/flask-internal:/app\" -w /app \\\n python:3.11-alpine sh -lc \"pip install --no-cache-dir flask && python app.py\"\n```\n\nSince this server has no Docker port forwarding configured, it is not reachable from outside and is only accessible from the esmsh-5558 container connected on the same network.\n\n4. Since both were running on localhost, I tested it through a Cloudflared tunnel to simulate external access.\n```bash\ncloudflared tunnel --url http://127.0.0.1:5558\n```\n\n5. Trigger SSRF from outside via esm.sh endpoint:\n```bash\ncurl -i \"https://ESM.SH_SERVER/http://127.0.0.1.nip.io:5555/secret.js\"\n```\n\n127.0.0.1 is blocked,\n<img width=\"1206\" height=\"322\" alt=\"image\" src=\"https://github.com/user-attachments/assets/054a7675-5b9e-461a-bb55-9ec7a2b2f43b\" />\n\n\nbut 127.0.0.1.nip.io bypasses the filter. \n<img width=\"1210\" height=\"336\" alt=\"image\" src=\"https://github.com/user-attachments/assets/95b991b1-ff93-495f-b624-458dd48fd5ff\" />\n\n\nThis confirms external requesters can fetch internal localhost service content through esm.sh.\n\n### Impact\nThis is a Server-Side Request Forgery vulnerability (CWE-918).\n\nImpacted:\n- Any esm.sh deployment exposing the `/http(s)` route to untrusted users.\n- Environments where internal services are reachable from the esm.sh server/container network.\n\nPotential consequences:\n- Access to localhost/internal HTTP services not intended for public access.\n- Internal service discovery/probing through the server.\n- Exposure of sensitive internal endpoints (deployment-dependent, e.g., metadata/internal admin APIs).\n- The exploit surface is extension-limited in this route (e.g., \".js\", \".ts\", \".mjs\", \".mts\", \".jsx\", \".tsx\", \".cjs\", \".cts\", \".vue\", \".svelte\", \".md\", \".css\"), so it is not a universal arbitrary-file fetch primitive.\n- Even with that limitation, **attackers can still verify whether internal HTTP services exist** and **retrieve internal JavaScript/Markdown resources (and similar allowed extension content) when present.**\n- If the internal server is implemented with Apache Tomcat, it may interpret everything after ; as a path parameter in a request such as /asdf/;asdf=a.js. As a result, **it could be possible to bypass extension checks while still receiving the response from the intended path.**",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "Go",
"name" : "github.com/esm-dev/esm.sh"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "0.0.0-20250616164159-0593516c4cfa"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/esm-dev/esm.sh/security/advisories/GHSA-p2v6-84h2-5x4r"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-27730"
}, {
"type" : "WEB",
"url" : "https://github.com/esm-dev/esm.sh/pull/1149"
}, {
"type" : "WEB",
"url" : "https://github.com/esm-dev/esm.sh/commit/0593516c4cfab49ad3b4900416a8432ff2e23eb0"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/esm-dev/esm.sh"
}, {
"type" : "WEB",
"url" : "https://github.com/esm-dev/esm.sh/releases/tag/v137"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-918" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-02-25T22:57:59Z",
"nvd_published_at" : "2026-02-25T16:23:27Z"
}
}