GHSA-FF3F-86QR-9CV3
Vulnerability from github – Published: 2026-09-30 15:40 – Updated: 2026-09-30 15:40A denial of service (DoS) vulnerability was identified in @angular/router when Server-Side Rendering (SSR) is enabled on Node.js (V8).
When @angular/router parses incoming request URLs, it extracts path segments, matrix parameters, and child outlets into plain JavaScript objects (Record<string, string>). When matrix parameter names or outlet names are numeric strings (such as /a;990;2522), the V8 JavaScript engine interprets them as array-indexed properties rather than named properties.
Under V8's internal property-storage heuristics, setting numeric keys on an initially empty object causes V8 to allocate a dense array backing store (HOLEY_ELEMENTS) sized to the maximum index rather than falling back to sparse dictionary storage. Specifically, assigning sequential or moderately large numeric keys (like 990 followed by 2522) causes V8 to allocate a contiguous backing store of ~2,522 pointers (~20 KB to 25 KB of heap) for a single 11-byte segment.
Because each segment in a URL path allocates its own independent parameters object, an attacker can craft URLs with repeated numeric matrix parameters to achieve an asymmetric memory amplification factor of approximately ~350x.
Impact
Successful exploitation allows an unauthenticated remote attacker to exhaust the Node.js old-space heap with modest request volume, terminating the SSR worker with an unrecoverable JavaScript heap out of memory fatal error and causing a Denial of Service.
- High Amplification: A single 11-byte segment (
/a;990;2522) consumes ~20 KB–25 KB of V8 heap. - Low Concurrency Required:
- With 8 KB request paths (~740 segments, within default Nginx 8 KB buffer limits), as few as 12–22 concurrent requests crash a 256 MiB–512 MiB Node.js SSR worker.
- With smaller 1 KB–2 KB request paths (~90–180 segments), a burst of ~50–100 concurrent requests achieves the same heap exhaustion.
- Client-side SPAs Unaffected: Pure client-side Angular applications (Single Page Applications without SSR) are not vulnerable, as local browser memory consumption does not cross a security boundary.
Attack Preconditions & Vulnerable Configurations
An application is affected only if all of the following conditions are met:
- SSR Enabled: The application runs in a Server-Side Rendering environment powered by Node.js / V8.
- Direct Router Parsing: User-controlled request URLs are parsed by
@angular/routerduring SSR. - No Reverse-Proxy Semicolon/Segment Filtering: Upstream reverse proxies (Nginx, Cloudflare, ALB) forward URLs containing semicolons (
;) and multiple path segments without stripping or rejecting them.
Exploit Payload Example
An attacker sends concurrent HTTP requests with repeated numeric matrix parameters:
GET /a;990;2522/a;990;2522/a;990;2522/... HTTP/1.1
Host: example.com
Even with paths under 2 KB, overlapping requests during SSR will rapidly consume the V8 heap until the process crashes.
Patches
The issue is resolved by updating @angular/router to enforce V8 dictionary elements storage (setUrlDerivedKey) for numeric URL-derived keys (index >= 32). This prevents V8 from allocating oversized contiguous array backing stores while preserving route matching, parameter values, and component input bindings.
22.2.021.2.2420.3.32
Workarounds & Mitigations
If you cannot immediately upgrade to a patched version, apply one of the following mitigations at your edge or reverse proxy:
- Block or Sanitize Matrix Parameters at the Reverse Proxy:
Configure your reverse proxy (e.g., Nginx, Cloudflare, or AWS WAF) to reject or strip semicolons (;) in request paths before forwarding requests to the Angular SSR service:nginx # Nginx example: reject requests containing matrix parameters if ($uri ~* ";") { return 400; } - Enforce Strict Path Segment Limits:
Reject requests with excessive path depth (e.g., more than 20–30 segments). - Increase Node.js Old Space:
Increase--max-old-space-size(e.g., to 2048 or 4096 MB) to increase the concurrency threshold required to exhaust memory, though this does not fully eliminate the vulnerability under sustained traffic.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "@angular/router"
},
"ranges": [
{
"events": [
{
"introduced": "22.0.0"
},
{
"fixed": "22.2.0"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "@angular/router"
},
"ranges": [
{
"events": [
{
"introduced": "21.0.0"
},
{
"fixed": "21.2.24"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "@angular/router"
},
"ranges": [
{
"events": [
{
"introduced": "20.0.0"
},
{
"fixed": "20.3.32"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "@angular/router"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"last_affected": "19.2.25"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-101896"
],
"database_specific": {
"cwe_ids": [
"CWE-400",
"CWE-770"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-30T15:40:47Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "A denial of service (DoS) vulnerability was identified in `@angular/router` when Server-Side Rendering (SSR) is enabled on Node.js (V8).\n\nWhen `@angular/router` parses incoming request URLs, it extracts path segments, matrix parameters, and child outlets into plain JavaScript objects (`Record\u003cstring, string\u003e`). When matrix parameter names or outlet names are numeric strings (such as `/a;990;2522`), the V8 JavaScript engine interprets them as array-indexed properties rather than named properties.\n\nUnder V8\u0027s internal property-storage heuristics, setting numeric keys on an initially empty object causes V8 to allocate a dense array backing store (`HOLEY_ELEMENTS`) sized to the maximum index rather than falling back to sparse dictionary storage. Specifically, assigning sequential or moderately large numeric keys (like `990` followed by `2522`) causes V8 to allocate a contiguous backing store of ~2,522 pointers (~20 KB to 25 KB of heap) for a single 11-byte segment.\n\nBecause each segment in a URL path allocates its own independent `parameters` object, an attacker can craft URLs with repeated numeric matrix parameters to achieve an asymmetric memory amplification factor of approximately **~350x**.\n\n### Impact\n\nSuccessful exploitation allows an unauthenticated remote attacker to exhaust the Node.js old-space heap with modest request volume, terminating the SSR worker with an unrecoverable `JavaScript heap out of memory` fatal error and causing a Denial of Service.\n\n* **High Amplification:** A single 11-byte segment (`/a;990;2522`) consumes ~20 KB\u201325 KB of V8 heap.\n* **Low Concurrency Required:**\n * With 8 KB request paths (~740 segments, within default Nginx 8 KB buffer limits), as few as **12\u201322 concurrent requests** crash a 256 MiB\u2013512 MiB Node.js SSR worker.\n * With smaller 1 KB\u20132 KB request paths (~90\u2013180 segments), a burst of ~50\u2013100 concurrent requests achieves the same heap exhaustion.\n* **Client-side SPAs Unaffected:** Pure client-side Angular applications (Single Page Applications without SSR) are **not** vulnerable, as local browser memory consumption does not cross a security boundary.\n\n### Attack Preconditions \u0026 Vulnerable Configurations\n\nAn application is affected only if **all** of the following conditions are met:\n\n* **SSR Enabled:** The application runs in a Server-Side Rendering environment powered by Node.js / V8.\n* **Direct Router Parsing:** User-controlled request URLs are parsed by `@angular/router` during SSR.\n* **No Reverse-Proxy Semicolon/Segment Filtering:** Upstream reverse proxies (Nginx, Cloudflare, ALB) forward URLs containing semicolons (`;`) and multiple path segments without stripping or rejecting them.\n\n#### Exploit Payload Example\n\nAn attacker sends concurrent HTTP requests with repeated numeric matrix parameters:\n\n```http\nGET /a;990;2522/a;990;2522/a;990;2522/... HTTP/1.1\nHost: example.com\n```\n\nEven with paths under 2 KB, overlapping requests during SSR will rapidly consume the V8 heap until the process crashes.\n\n### Patches\n\nThe issue is resolved by updating `@angular/router` to enforce V8 dictionary elements storage (`setUrlDerivedKey`) for numeric URL-derived keys (index \u003e= 32). This prevents V8 from allocating oversized contiguous array backing stores while preserving route matching, parameter values, and component input bindings.\n\n* `22.2.0`\n* `21.2.24`\n* `20.3.32`\n\n### Workarounds \u0026 Mitigations\n\nIf you cannot immediately upgrade to a patched version, apply one of the following mitigations at your edge or reverse proxy:\n\n1. **Block or Sanitize Matrix Parameters at the Reverse Proxy:** \n Configure your reverse proxy (e.g., Nginx, Cloudflare, or AWS WAF) to reject or strip semicolons (`;`) in request paths before forwarding requests to the Angular SSR service:\n ```nginx\n # Nginx example: reject requests containing matrix parameters\n if ($uri ~* \";\") {\n return 400;\n }\n ```\n2. **Enforce Strict Path Segment Limits:** \n Reject requests with excessive path depth (e.g., more than 20\u201330 segments).\n3. **Increase Node.js Old Space:** \n Increase `--max-old-space-size` (e.g., to 2048 or 4096 MB) to increase the concurrency threshold required to exhaust memory, though this does not fully eliminate the vulnerability under sustained traffic.",
"id": "GHSA-ff3f-86qr-9cv3",
"modified": "2026-09-30T15:40:47Z",
"published": "2026-09-30T15:40:47Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/angular/angular/security/advisories/GHSA-ff3f-86qr-9cv3"
},
{
"type": "WEB",
"url": "https://github.com/angular/angular/issues/70716"
},
{
"type": "WEB",
"url": "https://github.com/angular/angular/pull/70717"
},
{
"type": "WEB",
"url": "https://github.com/angular/angular/commit/03872a80bcf1c89b2b04cdd3f444b2ee954da583"
},
{
"type": "WEB",
"url": "https://github.com/angular/angular/commit/5af61216eab8bf4a6697a1d79bda3d857c06f89d"
},
{
"type": "WEB",
"url": "https://github.com/angular/angular/commit/ddfe21072ba32ca4cd9d7d3c6b7df66af81d58c4"
},
{
"type": "PACKAGE",
"url": "https://github.com/angular/angular"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Angular Server-Side Rendering (SSR): Denial of Service via Numeric URL Matrix Parameters"
}
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.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.