Skip to content

Bound nesting depth to prevent stack-overflow DoS (GHSA-82x6-q7mm-w9cf)#72

Merged
BinaryMuse merged 2 commits into
masterfrom
fix/nesting-depth-dos
Jul 13, 2026
Merged

Bound nesting depth to prevent stack-overflow DoS (GHSA-82x6-q7mm-w9cf)#72
BinaryMuse merged 2 commits into
masterfrom
fix/nesting-depth-dos

Conversation

@BinaryMuse

@BinaryMuse BinaryMuse commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Fixes security advisory GHSA-82x6-q7mm-w9cf: toml.parse() on deeply nested arrays or inline tables (a ~5 KB payload of a few thousand [ levels) drives the generated recursive-descent parser past Node's call-stack limit, crashing with an uncatchable RangeError. Since that's not a SyntaxError, application error handling that filters for parse errors rethrows it — a remote, unauthenticated DoS.

Fix

A depth guard on the value rule — the single point all array/inline-table recursion funnels through — caps nesting (default 500) and raises the library's normal parse error (.line/.column, via genError) once exceeded. The limit is configurable: toml.parse(input, { maxDepth }).

The counter is leak-free under PEG backtracking. The advisory's suggested ++depth … { depth-- } snippet decrements only on the success path, so every failed value probe (e.g. the one the parser backtracks over after a trailing comma) leaks an increment and can falsely reject valid input like a=[[1,],[2,],…]. value_choice here pairs the increment with a decrement on both the matching path (its action) and the non-matching path (a trailing predicate), so backtracking can't leak the counter — there's a regression test for exactly this.

The compiler recurses over the same AST but overflows later than the parser (survived depth ~2,200 while the parser crashed at ~2,500), so bounding parser depth also protects it.

maxDepth defaults to 500 — far beyond any realistic config nesting, well under the crash threshold, with headroom for smaller stacks (worker threads, reduced --stack-size). Setting it far higher re-exposes the overflow, so it's meant for lowering, not raising.

Deeply nested arrays or inline tables drove the recursive-descent parser
past the call-stack limit, crashing with an uncatchable RangeError
(GHSA-82x6-q7mm-w9cf). A depth guard on the `value` rule now caps nesting
(default 500) and raises a normal parse error instead. The limit is
configurable via `toml.parse(input, { maxDepth })`.

The guard pairs each increment with a decrement on both the matching and
backtracking paths, so a failed value probe (e.g. after a trailing comma)
cannot leak the counter and falsely reject valid input.
@BinaryMuse
BinaryMuse marked this pull request as ready for review July 13, 2026 22:04
@BinaryMuse
BinaryMuse merged commit b71c439 into master Jul 13, 2026
3 checks passed
@BinaryMuse
BinaryMuse deleted the fix/nesting-depth-dos branch July 13, 2026 22:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant