npm · @angular/platform-server
Angular: SSR XSS via Unescaped <template> Content Across DocumentFragment Boundaries in Fallback Raw-Content Elements
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.
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 (</tag -> </tag). However:
<template> element reside in a separate DocumentFragment (template.content), whose own parentNode is null.template.content, traversal terminated immediately at the DocumentFragment boundary.<noscript> or <iframe>) were not discovered. As a result, closing sequences like </noscript> within <template> content were emitted unescaped.{{ 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.<xmp> or <style> directly inside a component's <template> markup requires relaxed template schema checks (CUSTOM_ELEMENTS_SCHEMA or NO_ERRORS_SCHEMA). However, standard HTML comments and text nodes inside <template> within <noscript> are reachable without relaxed schemas.Renderer2 bypass template compiler schema checks entirely and are unconditionally affected.import { Component } from '@angular/core';
@Component({
selector: 'app-root',
standalone: true,
template: `
<noscript>
<template>
<xmp>{{ payload }}</xmp>
</template>
</noscript>
`
})
export class AppComponent {
// Attacker-controlled input bound via standard text interpolation
payload = '</noscript><img src=x onerror=alert("SSR_TEMPLATE_XSS")>';
}
Vulnerable SSR Output:
<noscript><template><xmp></noscript><img src=x onerror=alert("SSR_TEMPLATE_XSS")></xmp></template></noscript>
<template> elements nested within <noscript>, <iframe>, <noembed>, or <noframes> in server-rendered templates.<template> elements inside fallback containers when handling untrusted data.Is your project exposed to this? Stateward checks every dependency on every pull request and flags it only if your code actually reaches it.
Check my repoSources: CISA KEV (public domain), OSV.dev & GitHub Advisory Database (CC-BY-4.0), FIRST EPSS, NVD/CWE (public domain). Served live from the Stateward advisory database.