CVE-2026-88060: @angular/platform-server SSR XSS via Unescaped Template Content Across DocumentFragment Boundaries
Angular's server-side rendering serializer fails to escape closing tags like </noscript> when they appear inside a <template> element nested in a fallback container, letting an attacker break out of…

The problem
During SSR, Angular serializes the DOM to HTML string. To prevent breakout from fallback raw-content elements (noscript, iframe, noembed, noframes), the serializer walks a node's ancestor chain and escapes any matching closing tag sequences.
The bug: a <template> element's children live in a separate DocumentFragment whose parentNode is null. The serializer's ancestor walk stopped at that boundary and never saw the enclosing <noscript> (or similar). So a closing sequence like </noscript> inside <template> content was emitted raw, without escaping to </noscript>.
Any Angular SSR app that renders untrusted user input inside a <template> nested within a fallback container is affected. The impact is full client-side XSS: the unescaped closing tag prematurely ends the container and the trailing payload executes as live DOM.
Proof of concept
A working proof-of-concept for CVE-2026-88060 in @angular/platform-server, with the exact payload below.
<noscript>
<template>
<xmp></noscript><img src=x onerror=alert("SSR_TEMPLATE_XSS")></xmp>
</template>
</noscript>The attacker-controlled string </noscript><img src=x onerror=alert(...)> is bound via standard Angular text interpolation ({{ payload }}). On the server, the serializer emits it verbatim because ancestor traversal stops at the DocumentFragment boundary and never discovers the outer <noscript>.
The browser then parses the SSR HTML: the raw </noscript> closes the container early, and the <img onerror> fires as executable markup.
The patch (commits 73d8bbd, 89b2056, ba3bc47 on angular/angular and 04f987d on angular/domino) extends ancestor traversal to cross DocumentFragment boundaries by following fragment.host back to the <template> element and continuing up the outer tree. This way the enclosing <noscript> is discovered and </noscript> in the content is correctly escaped to </noscript>.
CWE-79 (XSS) and CWE-116 (improper output escaping) both apply. The root cause is incomplete ancestor traversal, not a missing sanitizer call, which is why Angular's standard interpolation guarantee was bypassed.
The fix
Upgrade @angular/platform-server to 22.1.4 or later. The fix is also backported to the 20.x and 21.x lines (see v20.3.30 and corresponding 21.x releases). If you cannot upgrade immediately, avoid rendering untrusted input inside any <template> element that is a descendant of <noscript>, <iframe>, <noembed>, or <noframes> in server-rendered components, and avoid building such DOM structures imperatively with Renderer2.
Reported by alan-agius4 (Angular team).
Related research
- highCVE-2026-69149CVE-2026-69149: @angular/platform-server SSR Fallback Raw-Content XSS
- highCVE-2026-88056CVE-2026-88056: @angular/platform-server SSRF via Unicode Whitespace Trim Bypass
- high · 8.1@eigenpal/docx-editor-core: CSS Injection and Print-Time XSS via Unescaped Font Name
- high · 8.2CVE-2026-84370CVE-2026-84370: svgo removeScripts Plugin XSS via Namespace and Control-Character Bypass