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.
395 vulnerabilities reference this CWE, most recent first.
GHSA-V6X5-CG8R-VV6X
Vulnerability from github – Published: 2026-04-02 20:30 – Updated: 2026-05-13 16:18Summary
Rack::Multipart::Parser#handle_mime_head parses quoted multipart parameters such as Content-Disposition: form-data; name="..." using repeated String#index searches combined with String#slice! prefix deletion. For escape-heavy quoted values, this causes super-linear processing.
An unauthenticated attacker can send a crafted multipart/form-data request containing many parts with long backslash-escaped parameter values to trigger excessive CPU usage during multipart parsing.
This results in a denial of service condition in Rack applications that accept multipart form data.
Details
Rack::Multipart::Parser#handle_mime_head parses quoted parameter values by repeatedly:
- Searching for the next quote or backslash,
- Copying the preceding substring into a new buffer, and
- Removing the processed prefix from the original string with
slice!.
An attacker can exploit this by sending a multipart request with many parts whose name parameters contain long escape-heavy values such as:
name="a\\a\\a\\a\\a\\..."
Under default Rack limits, a request can contain up to 4095 parts. If many of those parts use long quoted values with dense escape characters, the parser performs disproportionately expensive CPU work while remaining within normal request size and part-count limits.
Impact
Any Rack application that accepts multipart/form-data requests may be affected, including file upload endpoints and standard HTML form handlers.
An unauthenticated attacker can send crafted multipart requests that consume excessive CPU time during request parsing. Repeated requests can tie up application workers, reduce throughput, and degrade or deny service availability.
Mitigation
- Update to a patched version of Rack that parses quoted multipart parameters without repeated rescanning and destructive prefix deletion.
- Apply request throttling or rate limiting to multipart upload endpoints.
- Where operationally feasible, restrict or isolate multipart parsing on untrusted high-volume endpoints.
{
"affected": [
{
"package": {
"ecosystem": "RubyGems",
"name": "rack"
},
"ranges": [
{
"events": [
{
"introduced": "3.0.0.beta1"
},
{
"fixed": "3.1.21"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "RubyGems",
"name": "rack"
},
"ranges": [
{
"events": [
{
"introduced": "3.2.0"
},
{
"fixed": "3.2.6"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-34827"
],
"database_specific": {
"cwe_ids": [
"CWE-400",
"CWE-407",
"CWE-770"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-02T20:30:12Z",
"nvd_published_at": "2026-04-02T18:16:33Z",
"severity": "HIGH"
},
"details": "## Summary\n\n`Rack::Multipart::Parser#handle_mime_head` parses quoted multipart parameters such as `Content-Disposition: form-data; name=\"...\"` using repeated `String#index` searches combined with `String#slice!` prefix deletion. For escape-heavy quoted values, this causes super-linear processing.\n\nAn unauthenticated attacker can send a crafted `multipart/form-data` request containing many parts with long backslash-escaped parameter values to trigger excessive CPU usage during multipart parsing.\n\nThis results in a denial of service condition in Rack applications that accept multipart form data.\n\n## Details\n\n`Rack::Multipart::Parser#handle_mime_head` parses quoted parameter values by repeatedly:\n\n1. Searching for the next quote or backslash,\n2. Copying the preceding substring into a new buffer, and\n3. Removing the processed prefix from the original string with `slice!`.\n\nAn attacker can exploit this by sending a multipart request with many parts whose `name` parameters contain long escape-heavy values such as:\n\n```text\nname=\"a\\\\a\\\\a\\\\a\\\\a\\\\...\"\n```\n\nUnder default Rack limits, a request can contain up to 4095 parts. If many of those parts use long quoted values with dense escape characters, the parser performs disproportionately expensive CPU work while remaining within normal request size and part-count limits.\n\n## Impact\n\nAny Rack application that accepts `multipart/form-data` requests may be affected, including file upload endpoints and standard HTML form handlers.\n\nAn unauthenticated attacker can send crafted multipart requests that consume excessive CPU time during request parsing. Repeated requests can tie up application workers, reduce throughput, and degrade or deny service availability.\n\n## Mitigation\n\n* Update to a patched version of Rack that parses quoted multipart parameters without repeated rescanning and destructive prefix deletion.\n* Apply request throttling or rate limiting to multipart upload endpoints.\n* Where operationally feasible, restrict or isolate multipart parsing on untrusted high-volume endpoints.",
"id": "GHSA-v6x5-cg8r-vv6x",
"modified": "2026-05-13T16:18:33Z",
"published": "2026-04-02T20:30:12Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/rack/rack/security/advisories/GHSA-v6x5-cg8r-vv6x"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-34827"
},
{
"type": "PACKAGE",
"url": "https://github.com/rack/rack"
},
{
"type": "WEB",
"url": "https://github.com/rubysec/ruby-advisory-db/blob/master/gems/rack/CVE-2026-34827.yml"
}
],
"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": "Rack\u0027s multipart header parsing allows Denial of Service via escape-heavy quoted parameters"
}
GHSA-VCC3-GHJQ-M6FR
Vulnerability from github – Published: 2026-08-31 22:10 – Updated: 2026-08-31 22:10Impact
An attacker who can supply input to decodeUriComponent() (directly or via a dependency that uses this package on URL/query/path data) can cause excessive CPU usage and application unresponsiveness. This is an availability issue; there is no known memory corruption, data disclosure, or remote code execution impact.
Patches
Upgrade to decode-uri-component@0.5.0.
Workarounds
Limit the size of the input.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 0.4.2"
},
"package": {
"ecosystem": "npm",
"name": "decode-uri-component"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.5.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-45822"
],
"database_specific": {
"cwe_ids": [
"CWE-1176",
"CWE-400",
"CWE-405",
"CWE-407"
],
"github_reviewed": true,
"github_reviewed_at": "2026-08-31T22:10:20Z",
"nvd_published_at": "2026-06-30T09:16:25Z",
"severity": "MODERATE"
},
"details": "### Impact\nAn attacker who can supply input to `decodeUriComponent()` (directly or via a dependency that uses this package on URL/query/path data) can cause excessive CPU usage and application unresponsiveness. This is an availability issue; there is no known memory corruption, data disclosure, or remote code execution impact.\n\n### Patches\nUpgrade to `decode-uri-component@0.5.0`.\n\n### Workarounds\nLimit the size of the input.",
"id": "GHSA-vcc3-ghjq-m6fr",
"modified": "2026-08-31T22:10:20Z",
"published": "2026-08-31T22:10:20Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/SamVerschueren/decode-uri-component/security/advisories/GHSA-vcc3-ghjq-m6fr"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-45822"
},
{
"type": "WEB",
"url": "https://github.com/SamVerschueren/decode-uri-component/commit/fa479dafeede7bedf04e5c89aa78f2a78c664005"
},
{
"type": "PACKAGE",
"url": "https://github.com/SamVerschueren/decode-uri-component"
},
{
"type": "WEB",
"url": "https://github.com/SamVerschueren/decode-uri-component/blob/00662938dc7c6241547ae8abce7785cc13ffd3f6/index.js"
},
{
"type": "WEB",
"url": "https://github.com/SamVerschueren/decode-uri-component/releases/tag/v0.5.0"
},
{
"type": "WEB",
"url": "https://www.npmjs.com/package/decode-uri-component"
}
],
"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/E:U/S:N/AU:Y/R:U/V:D/RE:M/U:Amber",
"type": "CVSS_V4"
}
],
"summary": "decode-uri-component: Denial of service via exponential decoding of malformed percent-encoded input"
}
GHSA-VM5R-23W9-M8HX
Vulnerability from github – Published: 2026-09-13 12:31 – Updated: 2026-10-05 23:30Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-prgh-xp8r-p3m5. This link is maintained to preserve external references.
Original Description
Nodemailer versions 9.1.0 through 10.0.4 contain a quadratic time complexity vulnerability in the addressparser component when parsing email addresses with RFC 5322 comments. Attackers can craft malicious email headers with comment-separated atoms to consume excessive CPU and block the Node.js event loop for several seconds, causing denial of service.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "nodemailer"
},
"ranges": [
{
"events": [
{
"introduced": "9.1.0"
},
{
"last_affected": "10.0.4"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-407"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-05T23:30:00Z",
"nvd_published_at": "2026-09-13T12:17:16Z",
"severity": "HIGH"
},
"details": "## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of\u00a0GHSA-prgh-xp8r-p3m5. This link is maintained to preserve external references.\n\n## Original Description\nNodemailer versions 9.1.0 through 10.0.4 contain a quadratic time complexity vulnerability in the addressparser component when parsing email addresses with RFC 5322 comments. Attackers can craft malicious email headers with comment-separated atoms to consume excessive CPU and block the Node.js event loop for several seconds, causing denial of service.",
"id": "GHSA-vm5r-23w9-m8hx",
"modified": "2026-10-05T23:30:00Z",
"published": "2026-09-13T12:31:12Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/nodemailer/nodemailer/security/advisories/GHSA-prgh-xp8r-p3m5"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-90776"
},
{
"type": "WEB",
"url": "https://github.com/nodemailer/nodemailer/commit/c07f17518d25aca8ab2ad66968dcbca538c24b89"
},
{
"type": "WEB",
"url": "https://github.com/nodemailer/nodemailer"
},
{
"type": "WEB",
"url": "https://github.com/nodemailer/nodemailer/blob/v10.0.4/src/addressparser/index.ts#L251"
},
{
"type": "WEB",
"url": "https://github.com/nodemailer/nodemailer/releases/tag/v10.0.5"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/nodemailer-9.1.0-through-10.0.4-denial-of-service-via-quadratic-address-parsing"
}
],
"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"
},
{
"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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"type": "CVSS_V4"
}
],
"summary": "Duplicate Advisory: Nodemailer addressparser: O(n^2) on comment-joined addresses enables a remote DoS (reachable via mailparser)",
"withdrawn": "2026-10-05T23:30:00Z"
}
GHSA-VP2X-QP44-57V7
Vulnerability from github – Published: 2026-09-02 14:34 – Updated: 2026-09-02 14:34Summary
XMLCorpusView._read_xml_fragment() reads a corpus file in 1 KiB blocks, appending
each block to a growing fragment string, then calls _VALID_XML_RE.match(fragment)
on the full accumulated buffer every iteration. Because each iteration rescans the
entire accumulated fragment, the total amount of work grows quadratically with input
size.
Commit c9c332284 (CWE-1333) made each match() call linear. The quadratic behavior
is separate: the loop calls match() once per 1 KiB block, each time on a longer
buffer.
On the test system, an 8 MiB malformed XML file consumed approximately 48 CPU-seconds
through the public BNCCorpusReader.words() API with no source modification. Absolute
timings vary by hardware. _read_xml_fragment() imposes no limit on fragment size or
iteration count.
Details
File: nltk/corpus/reader/xmldocs.py
Function: XMLCorpusView._read_xml_fragment(), lines 261–308
The relevant loop:
fragment = ""
while True:
fragment += stream.read(self._BLOCK_SIZE) # grows by 1 KiB per iteration
if self._VALID_XML_RE.match(fragment): # rescans full buffer each time
return fragment
...
last_open_bracket = fragment.rfind("<")
if last_open_bracket > 0: # False for single-'<' payload
if self._VALID_XML_RE.match(fragment[:last_open_bracket]):
return ...
# loop continues
For a payload of b'<' + b'a' * (N-1):
- For this malformed input,
_VALID_XML_RE.match(fragment)does not succeed because the unterminated tag prevents the expression from matching before EOF. fragment.rfind("<")returns0; the guardlast_open_bracket > 0isFalse, so the backtrack branch is never taken.- The only exit is EOF, after all N bytes are consumed.
Affected readers -> readers that rely on XMLCorpusView, including
BNCCorpusReader, NPSChatCorpusReader, SemcorCorpusReader, MTECorpusReader,
NKJPCorpusReader, FrameNetCorpusReader, VerbNetCorpusReader, and direct
XMLCorpusView instantiation. XMLCorpusReader.xml() is not affected -> it calls
defusedxml.safe_parse().
PoC
Requires only pip install nltk. No corpus data needed.
from pathlib import Path
from tempfile import TemporaryDirectory
from time import perf_counter
from nltk.corpus.reader.bnc import BNCCorpusReader
SIZES_KIB = (256, 512, 1024, 2048, 4096, 8192)
results = []
with TemporaryDirectory() as directory:
root = Path(directory)
malformed = root / "unterminated.xml"
for kib in SIZES_KIB:
malformed.write_bytes(b"<" + b"a" * (kib * 1024 - 1))
t = perf_counter()
try:
list(BNCCorpusReader(str(root), [malformed.name]).words())
except ValueError as e:
assert "tag not closed" in str(e)
results.append(perf_counter() - t)
print("KiB seconds growth")
for i, (kib, elapsed) in enumerate(zip(SIZES_KIB, results)):
ratio = "-" if i == 0 else f"{elapsed / results[i-1]:.2f}x"
print(f"{kib:5d} {elapsed:9.3f} {ratio}")
Runtime should increase by approximately fourfold for each doubling of input size, although absolute timings vary by hardware.
During verification, _VALID_XML_RE.match() was instrumented to record the size of
each input. For a 256 KiB malformed file it was invoked 257 times on monotonically
increasing buffers (1024, 2048, …, 262144 bytes), with the final call occurring after
EOF. This confirms that every iteration rescans the accumulated fragment.
Impact
Applications that process attacker-controlled XML corpus files through an affected reader are vulnerable. The attacker needs only write access to a path the reader will open. No NLTK credentials or special privileges required. Offline tools reading only trusted local corpora are not at risk.
Affected versions: Verified in NLTK 3.9.4, 3.10.0, and the current develop branch.
Historical inspection indicates the same loop structure has existed since the
introduction of XMLCorpusView (2007), but only the listed versions were
experimentally verified. No patch exists in any published release.
This issue results in CPU exhaustion and may allow denial of service in applications that process attacker-controlled XML corpus files.
Suggested Fix
Avoid rescanning the accumulated fragment from the beginning after each 1 KiB read. Incremental parsing, bounded fragment accumulation, or another streaming approach would eliminate the quadratic behavior while preserving existing semantics.
A regression test should verify that BNCCorpusReader.words() raises ValueError
within a fixed timeout (e.g. 5 seconds) against a 2 MiB malformed input. The existing
test_xmldocs_security.py covers only the prior ReDoS payloads and does not exercise
this path.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 3.10.2"
},
"package": {
"ecosystem": "PyPI",
"name": "nltk"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.10.3"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-81723"
],
"database_specific": {
"cwe_ids": [
"CWE-400",
"CWE-407"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-02T14:34:11Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "## Summary\n\n`XMLCorpusView._read_xml_fragment()` reads a corpus file in 1 KiB blocks, appending\neach block to a growing `fragment` string, then calls `_VALID_XML_RE.match(fragment)`\non the full accumulated buffer every iteration. Because each iteration rescans the\nentire accumulated fragment, the total amount of work grows quadratically with input\nsize.\n\nCommit `c9c332284` (CWE-1333) made each `match()` call linear. The quadratic behavior\nis separate: the loop calls `match()` once per 1 KiB block, each time on a longer\nbuffer.\n\nOn the test system, an 8 MiB malformed XML file consumed approximately 48 CPU-seconds\nthrough the public `BNCCorpusReader.words()` API with no source modification. Absolute\ntimings vary by hardware. `_read_xml_fragment()` imposes no limit on fragment size or\niteration count.\n\n## Details\n\n**File:** `nltk/corpus/reader/xmldocs.py` \n**Function:** `XMLCorpusView._read_xml_fragment()`, lines 261\u2013308\n\nThe relevant loop:\n\n```python\nfragment = \"\"\nwhile True:\n fragment += stream.read(self._BLOCK_SIZE) # grows by 1 KiB per iteration\n if self._VALID_XML_RE.match(fragment): # rescans full buffer each time\n return fragment\n ...\n last_open_bracket = fragment.rfind(\"\u003c\")\n if last_open_bracket \u003e 0: # False for single-\u0027\u003c\u0027 payload\n if self._VALID_XML_RE.match(fragment[:last_open_bracket]):\n return ...\n # loop continues\n```\n\nFor a payload of `b\u0027\u003c\u0027 + b\u0027a\u0027 * (N-1)`:\n\n- For this malformed input, `_VALID_XML_RE.match(fragment)` does not succeed because\n the unterminated tag prevents the expression from matching before EOF.\n- `fragment.rfind(\"\u003c\")` returns `0`; the guard `last_open_bracket \u003e 0` is `False`, so\n the backtrack branch is never taken.\n- The only exit is EOF, after all N bytes are consumed.\n\n**Affected readers** -\u003e readers that rely on `XMLCorpusView`, including\n`BNCCorpusReader`, `NPSChatCorpusReader`, `SemcorCorpusReader`, `MTECorpusReader`,\n`NKJPCorpusReader`, `FrameNetCorpusReader`, `VerbNetCorpusReader`, and direct\n`XMLCorpusView` instantiation. `XMLCorpusReader.xml()` is not affected -\u003e it calls\n`defusedxml.safe_parse()`.\n\n## PoC\n\nRequires only `pip install nltk`. No corpus data needed.\n\n```python\nfrom pathlib import Path\nfrom tempfile import TemporaryDirectory\nfrom time import perf_counter\nfrom nltk.corpus.reader.bnc import BNCCorpusReader\n\nSIZES_KIB = (256, 512, 1024, 2048, 4096, 8192)\nresults = []\nwith TemporaryDirectory() as directory:\n root = Path(directory)\n malformed = root / \"unterminated.xml\"\n for kib in SIZES_KIB:\n malformed.write_bytes(b\"\u003c\" + b\"a\" * (kib * 1024 - 1))\n t = perf_counter()\n try:\n list(BNCCorpusReader(str(root), [malformed.name]).words())\n except ValueError as e:\n assert \"tag not closed\" in str(e)\n results.append(perf_counter() - t)\n\nprint(\"KiB seconds growth\")\nfor i, (kib, elapsed) in enumerate(zip(SIZES_KIB, results)):\n ratio = \"-\" if i == 0 else f\"{elapsed / results[i-1]:.2f}x\"\n print(f\"{kib:5d} {elapsed:9.3f} {ratio}\")\n```\n\nRuntime should increase by approximately fourfold for each doubling of input size,\nalthough absolute timings vary by hardware.\n\nDuring verification, `_VALID_XML_RE.match()` was instrumented to record the size of\neach input. For a 256 KiB malformed file it was invoked 257 times on monotonically\nincreasing buffers (1024, 2048, \u2026, 262144 bytes), with the final call occurring after\nEOF. This confirms that every iteration rescans the accumulated fragment.\n\n## Impact\n\nApplications that process attacker-controlled XML corpus files through an affected reader\nare vulnerable. The attacker needs only write access to a path the reader will open. No\nNLTK credentials or special privileges required. Offline tools reading only trusted\nlocal corpora are not at risk.\n\n**Affected versions:** Verified in NLTK 3.9.4, 3.10.0, and the current develop branch.\nHistorical inspection indicates the same loop structure has existed since the\nintroduction of `XMLCorpusView` (2007), but only the listed versions were\nexperimentally verified. No patch exists in any published release.\n\nThis issue results in CPU exhaustion and may allow denial of service in applications\nthat process attacker-controlled XML corpus files.\n\n## Suggested Fix\n\nAvoid rescanning the accumulated fragment from the beginning after each 1 KiB read.\nIncremental parsing, bounded fragment accumulation, or another streaming approach would\neliminate the quadratic behavior while preserving existing semantics.\n\nA regression test should verify that `BNCCorpusReader.words()` raises `ValueError`\nwithin a fixed timeout (e.g. 5 seconds) against a 2 MiB malformed input. The existing\n`test_xmldocs_security.py` covers only the prior ReDoS payloads and does not exercise\nthis path.",
"id": "GHSA-vp2x-qp44-57v7",
"modified": "2026-09-02T14:34:11Z",
"published": "2026-09-02T14:34:11Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/nltk/nltk/security/advisories/GHSA-vp2x-qp44-57v7"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-81723"
},
{
"type": "WEB",
"url": "https://github.com/nltk/nltk/commit/7808692d451b962711005d954859bb83aabcf8fa"
},
{
"type": "PACKAGE",
"url": "https://github.com/nltk/nltk"
},
{
"type": "WEB",
"url": "https://github.com/nltk/nltk/releases/tag/v3.10.3"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/nltk-before-3.10.3-quadratic-cpu-exhaustion-via-xmlcorpusview"
}
],
"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:L",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "NLTK: Quadratic CPU Exhaustion in `XMLCorpusView._read_xml_fragment()`"
}
GHSA-VRCR-9HJ9-JCG6
Vulnerability from github – Published: 2025-12-02 18:30 – Updated: 2026-06-05 14:34An issue was discovered in 5.2 before 5.2.9, 5.1 before 5.1.15, and 4.2 before 4.2.27.
Algorithmic complexity in django.core.serializers.xml_serializer.getInnerText() allows a remote attacker to cause a potential denial-of-service attack triggering CPU and memory exhaustion via specially crafted XML input processed by the XML Deserializer.
Earlier, unsupported Django series (such as 5.0.x, 4.1.x, and 3.2.x) were not evaluated and may also be affected.
Django would like to thank Seokchan Yoon for reporting this issue.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "Django"
},
"ranges": [
{
"events": [
{
"introduced": "5.2a1"
},
{
"fixed": "5.2.9"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "PyPI",
"name": "Django"
},
"ranges": [
{
"events": [
{
"introduced": "5.1a1"
},
{
"fixed": "5.1.15"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "PyPI",
"name": "Django"
},
"ranges": [
{
"events": [
{
"introduced": "4.2a1"
},
{
"fixed": "4.2.27"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2025-64460"
],
"database_specific": {
"cwe_ids": [
"CWE-407"
],
"github_reviewed": true,
"github_reviewed_at": "2025-12-03T16:59:02Z",
"nvd_published_at": "2025-12-02T16:15:56Z",
"severity": "MODERATE"
},
"details": "An issue was discovered in 5.2 before 5.2.9, 5.1 before 5.1.15, and 4.2 before 4.2.27.\nAlgorithmic complexity in `django.core.serializers.xml_serializer.getInnerText()` allows a remote attacker to cause a potential denial-of-service attack triggering CPU and memory exhaustion via specially crafted XML input processed by the XML `Deserializer`.\nEarlier, unsupported Django series (such as 5.0.x, 4.1.x, and 3.2.x) were not evaluated and may also be affected.\nDjango would like to thank Seokchan Yoon for reporting this issue.",
"id": "GHSA-vrcr-9hj9-jcg6",
"modified": "2026-06-05T14:34:31Z",
"published": "2025-12-02T18:30:35Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-64460"
},
{
"type": "WEB",
"url": "https://github.com/django/django/commit/0db9ea4669312f1f4973e09f4bca06ab9c1ec74b"
},
{
"type": "WEB",
"url": "https://github.com/django/django/commit/1dbd07a608e495a0c229edaaf84d58d8976313b5"
},
{
"type": "WEB",
"url": "https://github.com/django/django/commit/4d2b8803bebcdefd2b76e9e8fc528d5fddea93f0"
},
{
"type": "WEB",
"url": "https://github.com/django/django/commit/99e7d22f55497278d0bcb2e15e72ef532e62a31d"
},
{
"type": "WEB",
"url": "https://docs.djangoproject.com/en/dev/releases/security"
},
{
"type": "PACKAGE",
"url": "https://github.com/django/django"
},
{
"type": "WEB",
"url": "https://github.com/pypa/advisory-database/tree/main/vulns/django/PYSEC-2025-109.yaml"
},
{
"type": "WEB",
"url": "https://groups.google.com/g/django-announce"
},
{
"type": "WEB",
"url": "https://www.djangoproject.com/weblog/2025/dec/02/security-releases"
}
],
"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:L/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Django is vulnerable to DoS via XML serializer text extraction"
}
GHSA-VX57-287R-4H84
Vulnerability from github – Published: 2026-09-22 09:31 – Updated: 2026-09-22 18:33Net::IDN::Punycode versions before 2.590 for Perl allow CPU exhaustion via quadratic insertion cost when decoding a long label in decode_punycode.
The XS backend inserts each decoded code point into a UTF-8 buffer and finds the insertion point by scanning that buffer from the start, one character at a time. The scan runs once per code point over the output built so far, so the cost is quadratic in the label length. The pure-Perl backend downgrades its input to bytes so that substr can index it directly, but takes its working copy before the downgrade, so when the input carries the UTF-8 flag every substr on the copy scans from the start, with the same quadratic cost.
Nothing bounds the label length in the to-Unicode direction. The 63-byte DNS limit is checked only when converting to ASCII, so domain_to_unicode and uts46_to_unicode pass an attacker-supplied label of any length to the decoder.
{
"affected": [],
"aliases": [
"CVE-2026-87079"
],
"database_specific": {
"cwe_ids": [
"CWE-407"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-09-22T08:16:40Z",
"severity": "HIGH"
},
"details": "Net::IDN::Punycode versions before 2.590 for Perl allow CPU exhaustion via quadratic insertion cost when decoding a long label in decode_punycode.\n\nThe XS backend inserts each decoded code point into a UTF-8 buffer and finds the insertion point by scanning that buffer from the start, one character at a time. The scan runs once per code point over the output built so far, so the cost is quadratic in the label length. The pure-Perl backend downgrades its input to bytes so that substr can index it directly, but takes its working copy before the downgrade, so when the input carries the UTF-8 flag every substr on the copy scans from the start, with the same quadratic cost.\n\nNothing bounds the label length in the to-Unicode direction. The 63-byte DNS limit is checked only when converting to ASCII, so domain_to_unicode and uts46_to_unicode pass an attacker-supplied label of any length to the decoder.",
"id": "GHSA-vx57-287r-4h84",
"modified": "2026-09-22T18:33:28Z",
"published": "2026-09-22T09:31:12Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-87079"
},
{
"type": "WEB",
"url": "https://github.com/robrwo/Net-IDN-Encode/commit/00d723423b66810af26b88c552bedc61975b3078.patch"
},
{
"type": "WEB",
"url": "https://github.com/robrwo/Net-IDN-Encode/commit/447c6b38ef5d4570329fa4f78690f4e14e09ba0c.patch"
},
{
"type": "WEB",
"url": "https://metacpan.org/release/PJCJ/Net-IDN-Encode-2.590-TRIAL/changes"
},
{
"type": "WEB",
"url": "http://www.openwall.com/lists/oss-security/2026/09/22/14"
}
],
"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-W4CM-GVHJ-CGW6
Vulnerability from github – Published: 2026-09-23 21:23 – Updated: 2026-09-23 21:23AsyncParser can be forced to perform O(n^2) work on the length of the input. When a single JSON token arrives across many small chunks, each absorb call rescans the incomplete token from the start.
Impact
Denial of service via CPU exhaustion when parsing untrusted JSON.
Preconditions:
- Application uses AsyncParser
- Attacker can send large tokens with control over chunk sizes.
Patches
Fixed in jawn-parser-1.7.0.
Workarounds
If you can't upgrade immediately:
- Use the synchronous Parser.
- Buffer incoming bytes into larger chunks before calling absorb
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 1.6.0"
},
"package": {
"ecosystem": "Maven",
"name": "org.typelevel:jawn-parser_2.12"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.7.0"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 1.6.0"
},
"package": {
"ecosystem": "Maven",
"name": "org.typelevel:jawn-parser_2.13"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.7.0"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 1.6.0"
},
"package": {
"ecosystem": "Maven",
"name": "org.typelevel:jawn-parser_3"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.7.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-61814"
],
"database_specific": {
"cwe_ids": [
"CWE-400",
"CWE-407"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-23T21:23:58Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "`AsyncParser` can be forced to perform O(n^2) work on the length of the input. When a single JSON token arrives across many small chunks, each `absorb` call rescans the incomplete token from the start.\n\n### Impact\n\nDenial of service via CPU exhaustion when parsing untrusted JSON.\n\nPreconditions:\n- Application uses `AsyncParser`\n- Attacker can send large tokens with control over chunk sizes.\n\n### Patches\n\nFixed in jawn-parser-1.7.0.\n\n### Workarounds\n\nIf you can\u0027t upgrade immediately:\n- Use the synchronous `Parser`.\n- Buffer incoming bytes into larger chunks before calling `absorb`",
"id": "GHSA-w4cm-gvhj-cgw6",
"modified": "2026-09-23T21:23:59Z",
"published": "2026-09-23T21:23:58Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/typelevel/jawn/security/advisories/GHSA-w4cm-gvhj-cgw6"
},
{
"type": "WEB",
"url": "https://github.com/typelevel/jawn/commit/cddcd5e3387c356b05953f7b2af103d309687e4b"
},
{
"type": "PACKAGE",
"url": "https://github.com/typelevel/jawn"
},
{
"type": "WEB",
"url": "https://github.com/typelevel/jawn/releases/tag/v1.7.0"
}
],
"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": "Jawn: Quadratic parsing effort in AsyncParser"
}
GHSA-W4GW-W5JQ-G9JH
Vulnerability from github – Published: 2026-02-12 22:06 – Updated: 2026-02-12 22:06The html.Parse function in golang.org/x/net/html has quadratic parsing complexity when processing certain inputs, which can lead to Denial of Service (DoS) if an attacker provides specially crafted HTML content.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "golang.org/x/net/html"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.45.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2025-47911"
],
"database_specific": {
"cwe_ids": [
"CWE-407"
],
"github_reviewed": true,
"github_reviewed_at": "2026-02-12T22:06:13Z",
"nvd_published_at": "2026-02-05T18:16:09Z",
"severity": "MODERATE"
},
"details": "The html.Parse function in golang.org/x/net/html has quadratic parsing complexity when processing certain inputs, which can lead to Denial of Service (DoS) if an attacker provides specially crafted HTML content.",
"id": "GHSA-w4gw-w5jq-g9jh",
"modified": "2026-02-12T22:06:13Z",
"published": "2026-02-12T22:06:13Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-47911"
},
{
"type": "WEB",
"url": "https://github.com/golang/vulndb/issues/4440"
},
{
"type": "WEB",
"url": "https://go.dev/cl/709876"
},
{
"type": "PACKAGE",
"url": "https://go.googlesource.com/net"
},
{
"type": "WEB",
"url": "https://groups.google.com/g/golang-announce/c/jnQcOYpiR2c"
},
{
"type": "WEB",
"url": "https://pkg.go.dev/vuln/GO-2026-4440"
}
],
"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": "golang.org/x/net/html has a Quadratic Parsing Complexity issue"
}
GHSA-WF6M-89Q7-H9JH
Vulnerability from github – Published: 2023-07-26 21:30 – Updated: 2024-04-04 06:22Trustwave ModSecurity 3.x before 3.0.10 has Inefficient Algorithmic Complexity.
{
"affected": [],
"aliases": [
"CVE-2023-38285"
],
"database_specific": {
"cwe_ids": [
"CWE-407"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2023-07-26T21:15:10Z",
"severity": "HIGH"
},
"details": "Trustwave ModSecurity 3.x before 3.0.10 has Inefficient Algorithmic Complexity.",
"id": "GHSA-wf6m-89q7-h9jh",
"modified": "2024-04-04T06:22:21Z",
"published": "2023-07-26T21:30:20Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-38285"
},
{
"type": "WEB",
"url": "https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/modsecurity-v3-dos-vulnerability-in-four-transformations-cve-2023-38285"
},
{
"type": "WEB",
"url": "https://www.trustwave.com/en-us/resources/security-resources/software-updates/end-of-sale-and-trustwave-support-for-modsecurity-web-application-firewall"
}
],
"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-WP3C-266W-4QFQ
Vulnerability from github – Published: 2026-06-26 22:21 – Updated: 2026-06-26 22:21Summary
js-toml versions up to and including 1.1.0 parse hexadecimal / octal / binary integer literals via a hand-written parseBigInt loop that multiplies a BigInt accumulator by the radix once per input digit. Each iteration performs a BigInt * BigInt operation on an accumulator that grows linearly with the number of digits already consumed, so the whole loop is O(n²) in the literal length. The lexer regex places no upper bound on the literal length, so a single TOML document containing one ~500 kB hex literal pins one CPU core for ~40 seconds on a modern laptop (Apple M-series, Node v22). Memory amplification is bounded but CPU amplification is severe and grows quadratically: doubling the literal length quadruples the work.
A caller that invokes load() on attacker-controlled TOML (configuration upload endpoints, CI/CD systems ingesting third-party *.toml, IDE plugins, build tools) is exposed to a single-request CPU exhaustion DoS.
CWE-1333 (Inefficient Regular Expression Complexity → here, inefficient parser complexity), CWE-400 (Uncontrolled Resource Consumption), CWE-407 (Inefficient Algorithmic Complexity).
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H = 7.5 (HIGH) when the parser is invoked on attacker-controllable input; LOW when the calling application restricts TOML input size to small documents (< 1 kB).
Affected
- Package:
js-toml(npm) - Versions:
>= 0.0.0, <= 1.1.0(all released versions up to and including the current1.1.0) - Affected entry point:
load()exported from the package root
Vulnerable code
src/load/tokens/NonDecimalInteger.ts lines 54-84 at SHA-pinned 2470ebf2e9009096aa4cbd1a15e574c54cc36b1a:
const parseBigInt = (string: string, radix: number): bigint => {
let result = BigInt(0);
for (let i = 0; i < string.length; i++) {
const char = string[i];
const digit = parseInt(char, radix);
result = result * BigInt(radix) + BigInt(digit);
}
return result;
};
and the interpreter that dispatches to it at lines 72-84:
registerTokenInterpreter(NonDecimalInteger, (raw: string) => {
const intString = raw.replace(/_/g, '');
const digits = intString.slice(2);
const radix = getRadix(raw);
const int = parseInt(digits, radix);
if (Number.isSafeInteger(int)) {
return int;
}
return parseBigInt(digits, radix);
});
Two compounding problems:
-
Algorithmic: the loop performs
result * BigInt(radix) + BigInt(digit)once per input digit. AfteriiterationsresulthasO(i)limbs, so the multiply costsO(i). Summed overndigits the total cost isO(n²). -
No length guard: the lexer regex at
src/load/tokens/NonDecimalInteger.ts#L14-L46is0x<hexDigit>(<hexDigit>|_<hexDigit>)*(likewise for0o/0b). The literal length is bounded only by the input document size. There is nomaxNumberLength/maxLiteralLengthoption, nochevrotain-level cutoff, and no validation at the interpreter callsite.
By contrast, the DecimalInteger token interpreter at src/load/tokens/DecimalInteger.ts#L12-L19 uses the V8 native BigInt(intString) constructor, which is O(n) and runs in single-digit milliseconds for inputs that take 40 seconds via the hand-written radix loop.
Impact
A single attacker-supplied TOML document containing one ~500 kB radix-prefixed integer literal pins one CPU core for ~40 seconds on a modern laptop. Doubling the literal length quadruples the work. With 8 MB of input the parse would block the event loop for many minutes of CPU. In a typical Node.js single-thread process this blocks all concurrent request handling for the duration. The defect is exploitable on any code path that calls load() (the only documented entry point) on attacker-controlled or third-party TOML.
Reachability
The vulnerable path is the default code path for load(). No options or configuration are required to trigger it. Any caller that exposes load() to attacker-controlled or third-party TOML input reaches it on the first hex / octal / binary literal whose value exceeds Number.MAX_SAFE_INTEGER (i.e. more than 13 hex digits, 18 octal digits, or 53 binary digits).
Realistic exposure surfaces:
- Web service that accepts a user-supplied TOML configuration (settings import, theme upload, deployment manifest).
- CI / CD or build tool that runs
js-tomlon TOML in third-party repositories or pull requests. - IDE / language-server plugin that re-parses a TOML buffer on every keystroke.
- Multi-tenant SaaS that lets one tenant submit TOML processed by a shared worker.
PoC (End-to-end reproduction)
Environment
- Node.js
v22.x(tested onv22.0.0and Nodev26.0.0) - macOS arm64 / Linux x86_64 (CPU exhaustion is hardware-independent; absolute timings will scale by CPU clock)
Install
mkdir js-toml-cve && cd js-toml-cve
npm init -y
npm install js-toml@1.1.0 @iarna/toml
poc_full_e2e.mjs
import { load } from 'js-toml';
import iarna from '@iarna/toml';
function timeIt(label, fn) {
const t0 = process.hrtime.bigint();
let result, err;
try { result = fn(); } catch (e) { err = e; }
const t1 = process.hrtime.bigint();
const ms = (Number(t1 - t0) / 1e6).toFixed(1);
if (err) console.log(`${label}: ERROR ${err.message} after ${ms}ms`);
else console.log(`${label}: ${ms}ms${result ? ' ' + result : ''}`);
}
console.log('--- Sanity baseline (small inputs) ---');
timeIt('decimal int 1', () => { load('x = 1'); return ''; });
timeIt('hex 0x10', () => { load('x = 0x10'); return ''; });
timeIt('hex 0xffff', () => { load('x = 0xffff'); return ''; });
console.log('\n--- Amplification curve: js-toml.load() with 0x<N hex digits> ---');
for (const n of [10_000, 20_000, 50_000, 100_000, 200_000, 500_000]) {
const hexDigits = 'f'.repeat(n);
const tomlText = `x = 0x${hexDigits}`;
timeIt(`hex ${n.toLocaleString()} digits (${tomlText.length} bytes input)`,
() => {
const r = load(tomlText);
return `bits=${r.x.toString(2).length}`;
});
}
console.log('\n--- Negative control: same input via @iarna/toml ---');
for (const n of [10_000, 50_000, 100_000, 200_000]) {
const hexDigits = 'f'.repeat(n);
const tomlText = `x = 0x${hexDigits}`;
timeIt(`@iarna/toml hex ${n.toLocaleString()} digits`,
() => {
const r = iarna.parse(tomlText);
return `type=${typeof r.x}`;
});
}
console.log('\n--- Octal / binary share the same code path ---');
for (const n of [50_000, 100_000]) {
const octDigits = '7'.repeat(n);
const binDigits = '1'.repeat(n);
timeIt(`oct 0o${n.toLocaleString()} digits`,
() => { const r = load(`x = 0o${octDigits}`); return `bits=${r.x.toString(2).length}`; });
timeIt(`bin 0b${n.toLocaleString()} digits`,
() => { const r = load(`x = 0b${binDigits}`); return `bits=${r.x.toString(2).length}`; });
}
Captured run output (unpatched js-toml@1.1.0, Node v26.0.0, Apple M-series)
# js-toml version: 1.1.0
--- Sanity baseline (small inputs) ---
decimal int 1: 1.3ms
hex 0x10: 0.4ms
hex 0xffff: 0.1ms
--- Amplification curve: js-toml.load() with 0x<N hex digits> ---
hex 10,000 digits (10006 bytes input): 15.0ms bits=40000
hex 20,000 digits (20006 bytes input): 29.8ms bits=80000
hex 50,000 digits (50006 bytes input): 214.7ms bits=200000
hex 100,000 digits (100006 bytes input): 693.0ms bits=400000
hex 200,000 digits (200006 bytes input): 3239.6ms bits=800000
hex 500,000 digits (500006 bytes input): 40388.3ms bits=2000000
--- Negative control: same input via @iarna/toml ---
@iarna/toml hex 10,000 digits: 2.3ms type=bigint
@iarna/toml hex 50,000 digits: 3.2ms type=bigint
@iarna/toml hex 100,000 digits: 5.4ms type=bigint
@iarna/toml hex 200,000 digits: 10.2ms type=bigint
--- Octal / binary share the same code path ---
oct 0o50,000 digits: 187.6ms bits=150000
bin 0b50,000 digits: 49.5ms bits=50000
oct 0o100,000 digits: 633.2ms bits=300000
bin 0b100,000 digits: 196.8ms bits=100000
Confirmation points:
- Quadratic curve: 10k → 20k digits is ~2x time (15ms → 30ms); 100k → 200k is ~4.7x time (693ms → 3239ms); 200k → 500k (2.5x) is ~12x time (3.2s → 40s). Matches the predicted
O(n²). - Single ~500 kB document blocks the event loop for ~40 s of CPU time.
- Octal and binary literals trigger the same path through
parseBigInt(digits, 8)andparseBigInt(digits, 2). - The negative control (
@iarna/toml, which calls the V8 nativeBigInt(value)constructor) parses the same inputs in 2-10 ms. The defect is injs-toml's hand-written radix conversion, not in V8BigIntsemantics or in the input size itself.
Patched-build verification
After applying the fix (replace parseBigInt(digits, radix) with BigInt('0' + raw[1] + digits) and add a maxLiteralLength guard at the interpreter callsite), the same PoC produces:
--- Amplification curve: js-toml.load() with 0x<N hex digits> ---
hex 10,000 digits: 0.2ms bits=40000
hex 20,000 digits: 0.3ms bits=80000
hex 50,000 digits: 0.7ms bits=200000
hex 100,000 digits: 1.5ms bits=400000
hex 200,000 digits: 2.8ms bits=800000
hex 500,000 digits: 7.1ms bits=2000000
(Linear scaling, sub-10 ms even on inputs five orders of magnitude larger than any realistic literal.) With a 1000-digit cap applied at the interpreter callsite, literals beyond the cap raise SyntaxParseError instead of being parsed at all, matching the maxNumberLength convention used by jackson-core StreamReadConstraints and gson NumberLimits.
Suggested fix
Two changes, both in src/load/tokens/NonDecimalInteger.ts:
- Replace the hand-written
parseBigIntloop with the V8 nativeBigInt(prefixedString)constructor.BigIntnatively accepts the0x/0o/0bprefix and parses inO(n):
```ts registerTokenInterpreter(NonDecimalInteger, (raw: string) => { const intString = raw.replace(/_/g, ''); const digits = intString.slice(2); const radix = getRadix(raw);
// Optional but recommended: cap the literal length to avoid degenerate inputs
const MAX_RADIX_LITERAL_LENGTH = 1000;
if (digits.length > MAX_RADIX_LITERAL_LENGTH) {
throw new SyntaxParseError(
`Radix-prefixed integer literal exceeds ${MAX_RADIX_LITERAL_LENGTH} digits`
);
}
const int = parseInt(digits, radix);
if (Number.isSafeInteger(int)) {
return int;
}
// BigInt accepts '0x'/'0o'/'0b' prefix natively
return BigInt(intString);
}); ```
- Delete the
parseBigInthelper. The native constructor handles all three radices.
Either change alone fixes the worst-case wall-clock. The combination matches the constraint posture of jackson-core (StreamReadConstraints.validateIntegerLength) and gson (NumberLimits.checkNumberStringLength).
Fix PR link
https://github.com/sunnyadn/js-toml/commit/1abcb31dc7b1fa88e4c848a8d108891cfbb96fa2
Credit
Reported by tonghuaroot.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 1.1.0"
},
"package": {
"ecosystem": "npm",
"name": "js-toml"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.1.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-49293"
],
"database_specific": {
"cwe_ids": [
"CWE-1333",
"CWE-400",
"CWE-407"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-26T22:21:43Z",
"nvd_published_at": "2026-06-19T19:16:36Z",
"severity": "HIGH"
},
"details": "## Summary\n\n`js-toml` versions up to and including **1.1.0** parse hexadecimal / octal / binary integer literals via a hand-written `parseBigInt` loop that multiplies a `BigInt` accumulator by the radix once per input digit. Each iteration performs a `BigInt * BigInt` operation on an accumulator that grows linearly with the number of digits already consumed, so the whole loop is **O(n\u00b2)** in the literal length. The lexer regex places **no upper bound on the literal length**, so a single TOML document containing one ~500 kB hex literal pins one CPU core for **~40 seconds** on a modern laptop (Apple M-series, Node v22). Memory amplification is bounded but CPU amplification is severe and grows quadratically: doubling the literal length quadruples the work.\n\nA caller that invokes `load()` on attacker-controlled TOML (configuration upload endpoints, CI/CD systems ingesting third-party `*.toml`, IDE plugins, build tools) is exposed to a single-request CPU exhaustion DoS.\n\nCWE-1333 (Inefficient Regular Expression Complexity \u2192 here, inefficient parser complexity), CWE-400 (Uncontrolled Resource Consumption), CWE-407 (Inefficient Algorithmic Complexity).\n\nCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H = **7.5 (HIGH)** when the parser is invoked on attacker-controllable input; LOW when the calling application restricts TOML input size to small documents (\u003c 1 kB).\n\n## Affected\n\n- Package: `js-toml` (npm)\n- Versions: `\u003e= 0.0.0, \u003c= 1.1.0` (all released versions up to and including the current `1.1.0`)\n- Affected entry point: `load()` exported from the package root\n\n## Vulnerable code\n\n`src/load/tokens/NonDecimalInteger.ts` lines 54-84 at SHA-pinned [`2470ebf2e9009096aa4cbd1a15e574c54cc36b1a`](https://github.com/sunnyadn/js-toml/blob/2470ebf2e9009096aa4cbd1a15e574c54cc36b1a/src/load/tokens/NonDecimalInteger.ts#L54-L84):\n\n```ts\nconst parseBigInt = (string: string, radix: number): bigint =\u003e {\n let result = BigInt(0);\n for (let i = 0; i \u003c string.length; i++) {\n const char = string[i];\n const digit = parseInt(char, radix);\n result = result * BigInt(radix) + BigInt(digit);\n }\n\n return result;\n};\n```\n\nand the interpreter that dispatches to it at lines 72-84:\n\n```ts\nregisterTokenInterpreter(NonDecimalInteger, (raw: string) =\u003e {\n const intString = raw.replace(/_/g, \u0027\u0027);\n const digits = intString.slice(2);\n const radix = getRadix(raw);\n\n const int = parseInt(digits, radix);\n\n if (Number.isSafeInteger(int)) {\n return int;\n }\n\n return parseBigInt(digits, radix);\n});\n```\n\nTwo compounding problems:\n\n1. **Algorithmic**: the loop performs `result * BigInt(radix) + BigInt(digit)` once per input digit. After `i` iterations `result` has `O(i)` limbs, so the multiply costs `O(i)`. Summed over `n` digits the total cost is `O(n\u00b2)`.\n\n2. **No length guard**: the lexer regex at [`src/load/tokens/NonDecimalInteger.ts#L14-L46`](https://github.com/sunnyadn/js-toml/blob/2470ebf2e9009096aa4cbd1a15e574c54cc36b1a/src/load/tokens/NonDecimalInteger.ts#L14-L46) is `0x\u003chexDigit\u003e(\u003chexDigit\u003e|_\u003chexDigit\u003e)*` (likewise for `0o` / `0b`). The literal length is bounded only by the input document size. There is no `maxNumberLength` / `maxLiteralLength` option, no `chevrotain`-level cutoff, and no validation at the interpreter callsite.\n\nBy contrast, the `DecimalInteger` token interpreter at [`src/load/tokens/DecimalInteger.ts#L12-L19`](https://github.com/sunnyadn/js-toml/blob/2470ebf2e9009096aa4cbd1a15e574c54cc36b1a/src/load/tokens/DecimalInteger.ts#L12-L19) uses the V8 native `BigInt(intString)` constructor, which is `O(n)` and runs in single-digit milliseconds for inputs that take 40 seconds via the hand-written radix loop.\n\n## Impact\n\nA single attacker-supplied TOML document containing one ~500 kB radix-prefixed integer literal pins one CPU core for ~40 seconds on a modern laptop. Doubling the literal length quadruples the work. With `8 MB` of input the parse would block the event loop for many minutes of CPU. In a typical Node.js single-thread process this blocks all concurrent request handling for the duration. The defect is exploitable on any code path that calls `load()` (the only documented entry point) on attacker-controlled or third-party TOML.\n\n## Reachability\n\nThe vulnerable path is the default code path for `load()`. No options or configuration are required to trigger it. Any caller that exposes `load()` to attacker-controlled or third-party TOML input reaches it on the first hex / octal / binary literal whose value exceeds `Number.MAX_SAFE_INTEGER` (i.e. more than 13 hex digits, 18 octal digits, or 53 binary digits).\n\nRealistic exposure surfaces:\n\n- Web service that accepts a user-supplied TOML configuration (settings import, theme upload, deployment manifest).\n- CI / CD or build tool that runs `js-toml` on TOML in third-party repositories or pull requests.\n- IDE / language-server plugin that re-parses a TOML buffer on every keystroke.\n- Multi-tenant SaaS that lets one tenant submit TOML processed by a shared worker.\n\n## PoC (End-to-end reproduction)\n\n### Environment\n\n- Node.js `v22.x` (tested on `v22.0.0` and Node `v26.0.0`)\n- macOS arm64 / Linux x86_64 (CPU exhaustion is hardware-independent; absolute timings will scale by CPU clock)\n\n### Install\n\n```bash\nmkdir js-toml-cve \u0026\u0026 cd js-toml-cve\nnpm init -y\nnpm install js-toml@1.1.0 @iarna/toml\n```\n\n### `poc_full_e2e.mjs`\n\n```js\nimport { load } from \u0027js-toml\u0027;\nimport iarna from \u0027@iarna/toml\u0027;\n\nfunction timeIt(label, fn) {\n const t0 = process.hrtime.bigint();\n let result, err;\n try { result = fn(); } catch (e) { err = e; }\n const t1 = process.hrtime.bigint();\n const ms = (Number(t1 - t0) / 1e6).toFixed(1);\n if (err) console.log(`${label}: ERROR ${err.message} after ${ms}ms`);\n else console.log(`${label}: ${ms}ms${result ? \u0027 \u0027 + result : \u0027\u0027}`);\n}\n\nconsole.log(\u0027--- Sanity baseline (small inputs) ---\u0027);\ntimeIt(\u0027decimal int 1\u0027, () =\u003e { load(\u0027x = 1\u0027); return \u0027\u0027; });\ntimeIt(\u0027hex 0x10\u0027, () =\u003e { load(\u0027x = 0x10\u0027); return \u0027\u0027; });\ntimeIt(\u0027hex 0xffff\u0027, () =\u003e { load(\u0027x = 0xffff\u0027); return \u0027\u0027; });\n\nconsole.log(\u0027\\n--- Amplification curve: js-toml.load() with 0x\u003cN hex digits\u003e ---\u0027);\nfor (const n of [10_000, 20_000, 50_000, 100_000, 200_000, 500_000]) {\n const hexDigits = \u0027f\u0027.repeat(n);\n const tomlText = `x = 0x${hexDigits}`;\n timeIt(`hex ${n.toLocaleString()} digits (${tomlText.length} bytes input)`,\n () =\u003e {\n const r = load(tomlText);\n return `bits=${r.x.toString(2).length}`;\n });\n}\n\nconsole.log(\u0027\\n--- Negative control: same input via @iarna/toml ---\u0027);\nfor (const n of [10_000, 50_000, 100_000, 200_000]) {\n const hexDigits = \u0027f\u0027.repeat(n);\n const tomlText = `x = 0x${hexDigits}`;\n timeIt(`@iarna/toml hex ${n.toLocaleString()} digits`,\n () =\u003e {\n const r = iarna.parse(tomlText);\n return `type=${typeof r.x}`;\n });\n}\n\nconsole.log(\u0027\\n--- Octal / binary share the same code path ---\u0027);\nfor (const n of [50_000, 100_000]) {\n const octDigits = \u00277\u0027.repeat(n);\n const binDigits = \u00271\u0027.repeat(n);\n timeIt(`oct 0o${n.toLocaleString()} digits`,\n () =\u003e { const r = load(`x = 0o${octDigits}`); return `bits=${r.x.toString(2).length}`; });\n timeIt(`bin 0b${n.toLocaleString()} digits`,\n () =\u003e { const r = load(`x = 0b${binDigits}`); return `bits=${r.x.toString(2).length}`; });\n}\n```\n\n### Captured run output (unpatched `js-toml@1.1.0`, Node v26.0.0, Apple M-series)\n\n```\n# js-toml version: 1.1.0\n\n--- Sanity baseline (small inputs) ---\ndecimal int 1: 1.3ms\nhex 0x10: 0.4ms\nhex 0xffff: 0.1ms\n\n--- Amplification curve: js-toml.load() with 0x\u003cN hex digits\u003e ---\nhex 10,000 digits (10006 bytes input): 15.0ms bits=40000\nhex 20,000 digits (20006 bytes input): 29.8ms bits=80000\nhex 50,000 digits (50006 bytes input): 214.7ms bits=200000\nhex 100,000 digits (100006 bytes input): 693.0ms bits=400000\nhex 200,000 digits (200006 bytes input): 3239.6ms bits=800000\nhex 500,000 digits (500006 bytes input): 40388.3ms bits=2000000\n\n--- Negative control: same input via @iarna/toml ---\n@iarna/toml hex 10,000 digits: 2.3ms type=bigint\n@iarna/toml hex 50,000 digits: 3.2ms type=bigint\n@iarna/toml hex 100,000 digits: 5.4ms type=bigint\n@iarna/toml hex 200,000 digits: 10.2ms type=bigint\n\n--- Octal / binary share the same code path ---\noct 0o50,000 digits: 187.6ms bits=150000\nbin 0b50,000 digits: 49.5ms bits=50000\noct 0o100,000 digits: 633.2ms bits=300000\nbin 0b100,000 digits: 196.8ms bits=100000\n```\n\nConfirmation points:\n\n- Quadratic curve: 10k \u2192 20k digits is ~2x time (15ms \u2192 30ms); 100k \u2192 200k is ~4.7x time (693ms \u2192 3239ms); 200k \u2192 500k (2.5x) is ~12x time (3.2s \u2192 40s). Matches the predicted `O(n\u00b2)`.\n- Single ~500 kB document blocks the event loop for ~40 s of CPU time.\n- Octal and binary literals trigger the same path through `parseBigInt(digits, 8)` and `parseBigInt(digits, 2)`.\n- The negative control (`@iarna/toml`, which calls the V8 native `BigInt(value)` constructor) parses the same inputs in 2-10 ms. The defect is in `js-toml`\u0027s hand-written radix conversion, not in V8 `BigInt` semantics or in the input size itself.\n\n### Patched-build verification\n\nAfter applying the fix (replace `parseBigInt(digits, radix)` with `BigInt(\u00270\u0027 + raw[1] + digits)` and add a `maxLiteralLength` guard at the interpreter callsite), the same PoC produces:\n\n```\n--- Amplification curve: js-toml.load() with 0x\u003cN hex digits\u003e ---\nhex 10,000 digits: 0.2ms bits=40000\nhex 20,000 digits: 0.3ms bits=80000\nhex 50,000 digits: 0.7ms bits=200000\nhex 100,000 digits: 1.5ms bits=400000\nhex 200,000 digits: 2.8ms bits=800000\nhex 500,000 digits: 7.1ms bits=2000000\n```\n\n(Linear scaling, sub-10 ms even on inputs five orders of magnitude larger than any realistic literal.) With a 1000-digit cap applied at the interpreter callsite, literals beyond the cap raise `SyntaxParseError` instead of being parsed at all, matching the `maxNumberLength` convention used by `jackson-core` `StreamReadConstraints` and `gson` `NumberLimits`.\n\n## Suggested fix\n\nTwo changes, both in [`src/load/tokens/NonDecimalInteger.ts`](https://github.com/sunnyadn/js-toml/blob/2470ebf2e9009096aa4cbd1a15e574c54cc36b1a/src/load/tokens/NonDecimalInteger.ts):\n\n1. Replace the hand-written `parseBigInt` loop with the V8 native `BigInt(prefixedString)` constructor. `BigInt` natively accepts the `0x` / `0o` / `0b` prefix and parses in `O(n)`:\n\n ```ts\n registerTokenInterpreter(NonDecimalInteger, (raw: string) =\u003e {\n const intString = raw.replace(/_/g, \u0027\u0027);\n const digits = intString.slice(2);\n const radix = getRadix(raw);\n\n // Optional but recommended: cap the literal length to avoid degenerate inputs\n const MAX_RADIX_LITERAL_LENGTH = 1000;\n if (digits.length \u003e MAX_RADIX_LITERAL_LENGTH) {\n throw new SyntaxParseError(\n `Radix-prefixed integer literal exceeds ${MAX_RADIX_LITERAL_LENGTH} digits`\n );\n }\n\n const int = parseInt(digits, radix);\n if (Number.isSafeInteger(int)) {\n return int;\n }\n\n // BigInt accepts \u00270x\u0027/\u00270o\u0027/\u00270b\u0027 prefix natively\n return BigInt(intString);\n });\n ```\n\n2. Delete the `parseBigInt` helper. The native constructor handles all three radices.\n\nEither change alone fixes the worst-case wall-clock. The combination matches the constraint posture of `jackson-core` (`StreamReadConstraints.validateIntegerLength`) and `gson` (`NumberLimits.checkNumberStringLength`).\n\n## Fix PR link\n\nhttps://github.com/sunnyadn/js-toml/commit/1abcb31dc7b1fa88e4c848a8d108891cfbb96fa2\n\n## Credit\n\nReported by `tonghuaroot`.",
"id": "GHSA-wp3c-266w-4qfq",
"modified": "2026-06-26T22:21:43Z",
"published": "2026-06-26T22:21:43Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/sunnyadn/js-toml/security/advisories/GHSA-wp3c-266w-4qfq"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-49293"
},
{
"type": "WEB",
"url": "https://github.com/sunnyadn/js-toml/commit/1abcb31dc7b1fa88e4c848a8d108891cfbb96fa2"
},
{
"type": "PACKAGE",
"url": "https://github.com/sunnyadn/js-toml"
},
{
"type": "WEB",
"url": "https://github.com/sunnyadn/js-toml/releases/tag/v1.1.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": "js-toml vulnerable to CPU exhaustion via O(n^2) BigInt construction on radix-prefixed integer literals"
}
No mitigation information available for this CWE.
No CAPEC attack patterns related to this CWE.