CVE-2026-101895: Angular SSR DOCTYPE DoS, No Fix for Angular 5 to 19
How a truncated DOCTYPE declaration traps the SSR HTML parser in an infinite synchronous loop and freezes the Node.js process.

Angular published CVE-2026-101895 (GHSA-f67j-2jqw-jpq7) on September 9, 2026, a high-severity denial-of-service vulnerability in @angular/platform-server, the package that powers Angular server-side rendering. It carries a CVSS 4.0 score of 8.7 (CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N). A malformed DOCTYPE token that ends before the parser expects it to sends the SSR HTML parser into an infinite synchronous loop (CWE-835) that pegs the Node.js process at 100% CPU. On September 28 the advisory was reviewed into the GitHub Advisory Database and started appearing in scanner feeds, which is when most teams first saw it. Every release from 5.0.0-beta.6 through 19.2.25 is affected with no OSS fix available. Upstream fixed 20.3.31, 21.2.23, and 22.1.6, and HeroDevs NES for Angular ships patches for EOL lines down to v5.2.22.
Affected and unsupported? See NES for Angular.
What is CVE-2026-101895?
CVE-2026-101895 is an uncontrolled resource consumption vulnerability (CWE-400) rooted in an infinite loop with an unreachable exit condition (CWE-835) in the HTML parser Angular SSR uses on the server: domino, the DOM implementation bundled into @angular/platform-server.
The bug lives in how the tokenizer handles an incomplete DOCTYPE declaration. When the parser is in its after-DOCTYPE-name state and reaches end-of-input, the EOF branch emits its token without advancing the character pointer and without transitioning out of the state. So the scanner re-invokes the same handler, under the exact same conditions, and does it again, and again. There is no exit. Because Node.js runs on a single thread and the loop is fully synchronous, one poisoned render does not merely slow the server. It stops the event loop cold, and with it every other request the process was serving.
Root cause
The fault is in domino's tokenizer, in lib/HTMLParser.js, inside the after_doctype_name_state handler. Every tokenizer state ends by consuming a character and moving on. The EOF branch in this state does neither:
case -1: // EOF
forcequirks();
emitDoctype();
emitEOF();
break;It emits the DOCTYPE and EOF tokens but never advances nextchar and never transitions out of the state. The scanner loop calls the handler again, sees the same EOF, emits again, and loops forever. A correct handler advances the pointer or switches state so the parser actually terminates on end-of-input.
Reaching it takes one short, malformed string. The advisory's own reproducer is a standalone component that binds the payload to [innerHTML], so the server parses it during render:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
standalone: true,
template: `<div [innerHTML]="payload"></div>`,
})
export class AppComponent {
payload = '<!DOCTYPE html ';
}The payload is an unterminated DOCTYPE with a trailing space and nothing after it: <!DOCTYPE html . That trailing whitespace is what lands the tokenizer in the after-DOCTYPE-name state right as input runs out. In a real app the string does not come from a hardcoded property, it comes from whatever untrusted input your SSR path parses into the DOM.
Severity and exploit conditions
NVD has not yet analyzed this CVE, so the score below is the CNA (GitHub/Angular) assessment. Treat it as authoritative until NVD indexes its own analysis.
CVSS 4.0 base score: 8.7 (High). Vector: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N.
The precondition that matters: the attacker only needs a path by which their input reaches the server-side HTML parser. That means any SSR app that binds untrusted input into markup the server renders, through [innerHTML], server-side interpolation of attacker-influenced strings, or any content parsed into the DOM on the server before the response goes out. No authentication, no user interaction, one request.
Exploitation status
This advisory is already causing operational pain even without a live attacker. On September 29, one day after the advisory dropped, a public project (material-identity/schemas #325) filed an issue because its CI vulnerability scan started failing on main and on every pull request the moment this advisory (alongside sibling CVE-2026-88058) hit the feeds. Their app is pinned to Angular 19.2.25, the last 19.x release, and there is no 19-branch fix to bump to. Their only near-term option was to add the advisory IDs to a scanner ignore list and carry the finding as accepted risk. That is the real-world shape of an EOL DoS advisory: not a breach, but a red build on every PR and a decision about how long you are willing to ship with a known-unpatchable high severity in your SBOM.
What an attacker can do
A single crafted request that routes a malformed DOCTYPE token into the server-rendered DOM is enough to:
- Drive the Node.js process to 100% CPU and block the event loop, so the SSR server stops responding to all concurrent and subsequent requests, not just the malicious one.
- Take down a shared SSR instance for every user it serves, since one frozen event loop affects the whole process.
- Repeat cheaply. The payload is tiny and the effect is immediate, so an attacker can keep processes pinned across a fleet with trivial traffic, defeating naive restart-on-hang recovery.
The attack surface is any server-side render path that parses attacker-influenced content: user-supplied HTML bound to [innerHTML], server-interpolated markup, previews, comment rendering, imported documents, or any feature that reflects external content through the SSR DOM.
Who is affected?
The affected range covers @angular/platform-server from 5.0.0-beta.6 all the way through 19.2.25, plus the early 20.x, 21.x, and 22.x lines before their respective fixes. The Angular 19 line and everything below it are past end of life. Angular 19 security support ended May 19, 2026, and 18, 17, and earlier ended before that, so those branches will never receive an upstream fix.
If you are on a supported line, the fix is a patch bump. If you are on 19 or earlier, there is no upstream release to move to, and the standard advice to "just upgrade" means a major-version migration (or several) before you are out of range.
Mitigation guidance
Related CVEs
This advisory hit the GitHub Advisory Database on September 28 alongside a pair of sibling SSR flaws in the same package, and @angular/platform-server has been a recurring source of server-side issues this year:
- CVE-2026-88058: Angular SSR XSS via unescaped processing-instruction nodes in fallback raw-content elements, same package and same EOL lines.
- CVE-2026-88060: Angular SSR XSS via unescaped <template> content in fallback elements, executing across DocumentFragment boundaries.
- CVE-2026-50168: Angular SSR SSRF allowlist bypass via URL parser differential in @angular/platform-serve.
- CVE-2026-27739: SSRF and header injection in the Angular SSR request-handling pipeline.
If you run SSR on an EOL Angular line, assume this is a class of problem, not a one-off, and see our roundup of Angular's 2026 CVE surge.
Taking action
CVE-2026-101895 is cheap to trigger, unauthenticated, and capable of taking a whole SSR process offline with one request. If you are on a supported Angular line, patch to the fixed release and you are done. If you are on Angular 19 or earlier, the upstream door is closed: there is no OSS fix for your branch, and the CI scanners already know it, which is why teams on 19.2.25 are watching their builds go red with nothing to bump to.
HeroDevs NES for Angular exists for exactly that gap. It provides maintained, drop-in security patches for EOL Angular lines down to v5.2.22, so you can resolve this CVE (and the sibling SSR advisories shipping alongside it) and clear the finding from your SBOM without committing to a major-version migration on someone else's timeline. See NES for Angular to check coverage for your version.
Resources
View All Articles
%20for%20Axios.webp)

