GHSA-V3P8-WHQ6-R5JG
Vulnerability from github – Published: 2026-09-10 20:25 – Updated: 2026-09-10 20:25Summary
An XSS vulnerability exists in @angular/platform-server during server-side rendering (SSR) HTML serialization when traversing ancestor tags across <template> element boundaries. When an application renders untrusted user input within raw-text tags (<xmp>, <style>, <script>), comments, or text nodes inside a <template> that is nested within a fallback raw-content element (<noscript>, <iframe>, <noembed>, <noframes>), matching closing tags (e.g., </noscript>) are not escaped during HTML serialization. When rendered in a browser, this unescaped closing tag prematurely terminates the fallback container and executes trailing markup as active DOM elements.
Technical Description
In HTML5 parsing, fallback raw-content elements (<noscript>, <iframe>, <noembed>, <noframes>) place the browser's tokenizer into RAWTEXT mode. In this mode, inner content is parsed as literal text until an end tag matching the container tag name (e.g., </noscript>) is encountered.
To prevent XSS breakout vectors during SSR serialization, the DOM serializer inspects a node's ancestors to escape any matching fallback closing tags (`
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "@angular/platform-server"
},
"ranges": [
{
"events": [
{
"introduced": "22.0.0"
},
{
"fixed": "22.1.4"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "@angular/platform-server"
},
"ranges": [
{
"events": [
{
"introduced": "21.0.0"
},
{
"fixed": "21.2.22"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "@angular/platform-server"
},
"ranges": [
{
"events": [
{
"introduced": "20.0.0"
},
{
"fixed": "20.3.30"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "@angular/platform-server"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"last_affected": "19.2.25"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-88060"
],
"database_specific": {
"cwe_ids": [
"CWE-79",
"CWE-116"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-10T20:25:25Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "### Summary\nAn XSS vulnerability exists in `@angular/platform-server` during server-side rendering (SSR) HTML serialization when traversing ancestor tags across `\u003ctemplate\u003e` element boundaries. When an application renders untrusted user input within raw-text tags (`\u003cxmp\u003e`, `\u003cstyle\u003e`, `\u003cscript\u003e`), comments, or text nodes inside a `\u003ctemplate\u003e` that is nested within a fallback raw-content element (`\u003cnoscript\u003e`, `\u003ciframe\u003e`, `\u003cnoembed\u003e`, `\u003cnoframes\u003e`), matching closing tags (e.g., `\u003c/noscript\u003e`) are not escaped during HTML serialization. When rendered in a browser, this unescaped closing tag prematurely terminates the fallback container and executes trailing markup as active DOM elements.\n\n### Technical Description\nIn HTML5 parsing, fallback raw-content elements (`\u003cnoscript\u003e`, `\u003ciframe\u003e`, `\u003cnoembed\u003e`, `\u003cnoframes\u003e`) place the browser\u0027s tokenizer into `RAWTEXT` mode. In this mode, inner content is parsed as literal text until an end tag matching the container tag name (e.g., `\u003c/noscript\u003e`) is encountered.\n\nTo prevent XSS breakout vectors during SSR serialization, the DOM serializer inspects a node\u0027s ancestors to escape any matching fallback closing tags (`\u003c/tag` -\u003e `\u0026lt;/tag`). However:\n1. Per DOM specifications, the children of a `\u003ctemplate\u003e` element reside in a separate `DocumentFragment` (`template.content`), whose own `parentNode` is `null`.\n2. The serializer\u0027s ancestor traversal previously only inspected element nodes. When traversing upward from a node inside `template.content`, traversal terminated immediately at the `DocumentFragment` boundary.\n3. Because traversal stopped before reaching the outer document tree, enclosing fallback raw-content ancestors (such as `\u003cnoscript\u003e` or `\u003ciframe\u003e`) were not discovered. As a result, closing sequences like `\u003c/noscript\u003e` within `\u003ctemplate\u003e` content were emitted unescaped.\n\n### Impact \u0026 Reachability\n* **Framework Guarantee Bypass**: Angular guarantees that standard text interpolation (`{{ userInput }}` bound as element text content) is safe by default without manual sanitization. This vulnerability bypasses that guarantee during SSR HTML serialization when untrusted input is interpolated inside template content within fallback containers.\n* **Template Authoring**: Writing literal `\u003cxmp\u003e` or `\u003cstyle\u003e` directly inside a component\u0027s `\u003ctemplate\u003e` markup requires relaxed template schema checks (`CUSTOM_ELEMENTS_SCHEMA` or `NO_ERRORS_SCHEMA`). However, standard HTML comments and text nodes inside `\u003ctemplate\u003e` within `\u003cnoscript\u003e` are reachable without relaxed schemas.\n* **Imperative DOM Construction**: Components or directives that construct DOM structures imperatively via `Renderer2` bypass template compiler schema checks entirely and are unconditionally affected.\n\n### Proof of Concept (Minimal Reproduction)\n```ts\nimport { Component } from \u0027@angular/core\u0027;\n\n@Component({\n selector: \u0027app-root\u0027,\n standalone: true,\n template: `\n \u003cnoscript\u003e\n \u003ctemplate\u003e\n \u003cxmp\u003e{{ payload }}\u003c/xmp\u003e\n \u003c/template\u003e\n \u003c/noscript\u003e\n `\n})\nexport class AppComponent {\n // Attacker-controlled input bound via standard text interpolation\n payload = \u0027\u003c/noscript\u003e\u003cimg src=x onerror=alert(\"SSR_TEMPLATE_XSS\")\u003e\u0027;\n}\n```\n**Vulnerable SSR Output**:\n```html\n\u003cnoscript\u003e\u003ctemplate\u003e\u003cxmp\u003e\u003c/noscript\u003e\u003cimg src=x onerror=alert(\"SSR_TEMPLATE_XSS\")\u003e\u003c/xmp\u003e\u003c/template\u003e\u003c/noscript\u003e\n```\n\n### Workarounds\n* Avoid rendering untrusted user input inside `\u003ctemplate\u003e` elements nested within `\u003cnoscript\u003e`, `\u003ciframe\u003e`, `\u003cnoembed\u003e`, or `\u003cnoframes\u003e` in server-rendered templates.\n* Avoid programmatic DOM assembly of `\u003ctemplate\u003e` elements inside fallback containers when handling untrusted data.",
"id": "GHSA-v3p8-whq6-r5jg",
"modified": "2026-09-10T20:25:25Z",
"published": "2026-09-10T20:25:25Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/angular/angular/security/advisories/GHSA-v3p8-whq6-r5jg"
},
{
"type": "WEB",
"url": "https://github.com/angular/angular/commit/73d8bbd27cb46495426d4132975a1355b47ad915"
},
{
"type": "WEB",
"url": "https://github.com/angular/angular/commit/89b20568dfaee1ec8e0b3bcf1872acdddd2f4fef"
},
{
"type": "WEB",
"url": "https://github.com/angular/angular/commit/ba3bc47b20b3d12f5eb141ec9c651373ae4d15e8"
},
{
"type": "WEB",
"url": "https://github.com/angular/domino/commit/04f987dc08ff3736b427f50941adf1722458528f"
},
{
"type": "PACKAGE",
"url": "https://github.com/angular/angular"
},
{
"type": "WEB",
"url": "https://github.com/angular/angular/releases/tag/v20.3.30"
},
{
"type": "WEB",
"url": "https://github.com/angular/angular/releases/tag/v21.2.22"
},
{
"type": "WEB",
"url": "https://github.com/angular/angular/releases/tag/v22.1.4"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Angular: SSR XSS via Unescaped \u003ctemplate\u003e Content Across DocumentFragment Boundaries in Fallback Raw-Content Elements"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.