Marko: XSS via case-insensitive script/style closing tag bypass in runtime HTML escaping
Date: 2026-04
Affected: npm/@marko/runtime-tags, npm/marko
Attack vector: xss
Category: vulnerability-disclosure
Severity: Medium
CVE: CVE-2026-41591
CWE: CWE-79
CVSS: 6.4 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N)

### Summary When dynamic text is interpolated into a `<script>` or `<style>` tag the Marko runtime failed to prevent tag breakout when the closing tag used non-lowercase casing. An attacker able to place input inside a `<script>` or `<style>` block could break out of the tag with `</SCRIPT>`, `</Style>`, etc. and inject arbitrary HTML/JavaScript, resulting in cross-site scripting. ### Details The affected helpers used case-sensitive regular expressions to detect attempts at closing the surrounding tag: ```js // packages/runtime-tags/src/html/content.ts const unsafeScriptReg = /<\/script/g; const unsafeStyleReg = /<\/style/g; // packages/runtime-class/src/runtime/html/helpers/escape-script-placeholder.js const unsafeCharsReg = /<\/script/g; // packages/runtime-class/src/runtime/html/helpers/escape-style-placeholder.js const unsafeCharsReg = /<\/style/g; ``` HTML tag names are case-insensitive in the browser parser, so inputs such as `</SCRIPT>`, `</Script>`, or `</sTyLe>` were not matched by these regexes and passed through the helpers unchanged. A browser rendering the output treats the mixed-case end tag as a valid closing tag, terminating the script or style context, and then parses anything that follows as HTML. The Marko compiler routes interpolated values inside `<script>` and `<style>` tags through these helpers automatically (see `native-tag.ts:1080-1085`), so application code following the framework's conventions had no way to detect or compensate for the gap


Mitigations:

Architectural layers (MAESTRO):