GHSA-f67j-2jqw-jpq7: Npm/@angular/platform-server vulnerability
A Denial of Service (DoS) vulnerability exists in @angular/platform-server's DOM emulation parser (domino). When processing untrusted user input containing an incomplete DOCTYPE declaration ending with whitespace before EOF (such as <!DOCTYPE html ), the HTML parser enters an infinite synchronous loop, pegging CPU utilization at 100% and completely freezing the Node.js server process.
Technical Description In Angular Server-Side Rendering (SSR), @angular/platform-server uses domino to parse and sanitize HTML bound through template bindings (such as [innerHTML]) or manipulated via DOM APIs.
In Domino's HTML parser (lib/HTMLParser.js), tokenizer states that specify fixed lookahead—such as afterdoctypenamestate (lookahead = 6)—rely on the state handler function to explicitly advance the character index pointer (nextchar). While branches for whitespace, >, and keyword matching advance nextchar, the EOF branch (case -1: // EOF) emitted doctype and EOF tokens without advancing nextchar or transitioning out of the state:
javascript case -1: // EOF forcequirks(); emitDoctype(); emitEOF(); break;
Because nextchar remained unchanged pointing to the EOF marker character (\uFFFF), the scanner loop (while (nextchar < numchars)) repeatedly re-invoked afterdoctypenamestate with codepoint = EOF indefinitely. In Node.js's single-threaded runtime, this synchronous loop starves the event loop entirely.
Impact & Reachability Reachability: The vulnerability is reachable in any Angular SSR application where untrusted user input is bound to [innerHTML], interpolated into markup, or sanitized on the server. Impact: Successful exploitation allows an unauthenticated remote attacker to cause an immediate Denial of Service (DoS) by sending a payload containing an incomplete DOCTYPE (e.g., <!DOCTYPE html ). The Node.js SSR process locks up at 100% CPU and ceases responding to all concurrent and subsequent HTTP requests.
Proof of Concept: ts import { Component } from '@angular/core';
@Component({ selector: 'app-root', standalone: true, template: <div [innerHTML]="payload"></div>, }) export class AppComponent { // Attacker-controlled input containing an incomplete DOCTYPE ending with whitespace payload = '<!DOCTYPE html '; }
Workarounds Avoid binding untrusted user input directly to [innerHTML] in server-rendered templates; use standard text interpolation ({{ userInput }}) or [textContent] when raw HTML rendering is not required. Validate or sanitize user input before passing it to [innerHTML] on the server by stripping or rejecting strings matching /^<!DOCTYPE/i.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
npm/@angular/platform-serverto a version that resolves this vulnerability.Fixed in 20.3.31 - Upgrade
Upgrade
npm/@angular/platform-serverto a version that resolves this vulnerability.Fixed in 21.2.23 - Upgrade
Upgrade
npm/@angular/platform-serverto a version that resolves this vulnerability.Fixed in 22.1.6 - Compensating control
In Angular SSR templates, avoid binding untrusted user input directly to [innerHTML]; use standard text interpolation ({{ userInput }}) or [textContent] when raw HTML rendering is not required.
- Compensating control
Before passing user input to [innerHTML] on the server, validate or sanitize it by stripping or rejecting strings matching /^<!DOCTYPE/i.