esm.sh CDN service has JS Template Literal Injection in CSS-to-JavaScript
Description
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.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/esm-dev/esm.shGo | < 0.0.0-20251118065157-87d2f6497574 | 0.0.0-20251118065157-87d2f6497574 |
Affected products
1Patches
187d2f6497574Fix `*.css?module` XSS (#1237)
2 files changed · +12 −14
server/router.go+7 −9 modified@@ -1069,17 +1069,15 @@ func esmRouter(db Database, esmStorage storage.Storage, logger *log.Logger) rex. if err != nil { return rex.Status(500, err.Error()) } + css, err = minify(string(css), esbuild.LoaderCSS, esbuild.ES2022) + 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, '`') { - buf.WriteString("stylesheet.replaceSync(`") - buf.WriteString(strings.TrimSpace(string(utils.MustEncodeJSON(string(css))))) - buf.WriteString(");\n") - } else { - buf.WriteString("stylesheet.replaceSync(`") - buf.Write(css) - buf.WriteString("`);\n") - } + buf.WriteString("stylesheet.replaceSync(") + buf.WriteString(strings.TrimSuffix(string(utils.MustEncodeJSON(strings.TrimSuffix(string(css), "\n"))), "\n")) + buf.WriteString(");\n") buf.WriteString("export default stylesheet;\n") ctx.SetHeader("Content-Type", ctJavaScript) ctx.SetHeader("Content-Length", fmt.Sprintf("%d", buf.Len()))
test/issue-1191/test.ts+5 −5 modified@@ -1,4 +1,4 @@ -import { assert, assertEquals } from "jsr:@std/assert"; +import { assert, assertEquals, assertStringIncludes } from "jsr:@std/assert"; // related issue: https://github.com/esm-dev/esm.sh/issues/1191 Deno.test( @@ -9,7 +9,7 @@ Deno.test( assert(res.ok); assertEquals(res.headers.get("content-type"), "application/javascript; charset=utf-8"); assertEquals(res.headers.get("cache-control"), "public, max-age=31536000, immutable"); - assert(text.includes(`import("/aleman@1.0.7/style.css?module")`)); + assertStringIncludes(text, `import("/aleman@1.0.7/style.css?module")`); }, ); @@ -21,8 +21,8 @@ Deno.test( assert(res.ok); assertEquals(res.headers.get("content-type"), "application/javascript; charset=utf-8"); assertEquals(res.headers.get("cache-control"), "public, max-age=31536000, immutable"); - assert(text.includes("const stylesheet = new CSSStyleSheet();")); - assert(text.includes("stylesheet.replaceSync(`")); - assert(text.includes("`);\nexport default stylesheet;")); + assertStringIncludes(text, "const stylesheet = new CSSStyleSheet();"); + assertStringIncludes(text, "stylesheet.replaceSync("); + assertStringIncludes(text, ");\nexport default stylesheet;"); }, );
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-hcpf-qv9m-vfgpghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-65026ghsaADVISORY
- github.com/esm-dev/esm.sh/commit/87d2f6497574bf4448641a5527a3ac2beba5fd6cghsax_refsource_MISCWEB
- github.com/esm-dev/esm.sh/security/advisories/GHSA-hcpf-qv9m-vfgpghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.