GitHub Advisoryadvisory · npm

GHSA-8pfc-jjgw-6g26 SandboxJS: Stack overflow DoS via deeply nested expressions in recursive descent parser

MEDIUM Published 2026-04-03 Modified 2026-04-06 Official page ↗
Aliases
CVE-2026-34211
Ecosystem
npm
Packages
@nyariv/sandboxjs
Severity
Moderate
CVSS vector
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
Weaknesses
CWE-674
Reviewed
2026-04-03

Affected packages

• npm @nyariv/sandboxjs: < 0.8.36 → fixed in 0.8.36

Description

Summary The `@nyariv/sandboxjs` parser contains unbounded recursion in the `restOfExp` function and the `lispify`/`lispifyExpr` call chain. An attacker can crash any Node.js process that parses untrusted input by supplying deeply nested expressions (e.g., ~2000 nested parentheses), causing a `RangeError: Maximum call stack size exceeded` that terminates the process. Details The root cause is in `src/parser.ts`. The `restOfExp` function (line 443) iterates through expression characters, and when it encounters a closing bracket that doesn't match the expected `firstOpening`, it recursively calls itself at line 503: ```typescript // src/parser.ts:486-505 } else if (closings[char]) { // ... if (char === firstOpening) { done = true; break; } else { const skip = restOfExp(constants, part.substring(i + 1), [], char); // line 503 cache.set(skip.start - 1, skip.end); i += skip.length + 1; } } ``` Each nested bracket (`(`, `[`, `{`) adds a stack frame. There is no depth counter or limit check. The function signature has no depth parameter: ```typescript export function restOfExp( constants: IConstants, part: CodeString, tests?: RegExp[], quote?: string, firstOpening?: string, closingsTests?: RegExp[], details: restDetails = {}, ): CodeString { ``` A second unbounded recursive path exists through `lispify` → `lispTypes.get(type)` → `group` handler → `lispifyExpr` (line 672) → `lispify`, which processes parenthesized groups recursively with no depth limit. All public API methods (`Sandbox.parse()`, `Sandbox.compile()`, `Sandbox.compileAsync()`, `Sandbox.compileExpression()`, `Sandbox.compileExpressionAsync()`) pass user input directly to `parse()` with no input validation or depth limiting. A `RangeError: Maximum call stack size exceeded` in Node.js is not a catchable exception in the normal sense — it crashes the current execution context and, in a server handling requests synchronously, can crash the entire process. PoC ```bash Install the package npm install @nyariv/sandboxjs Create test file cat > poc.js MAX_PARSE_DEPTH) { throw new ParseError('Expression nesting depth exceeded', part.toString()); } // ... existing code ... // At line 503, pass depth + 1: const skip = restOfExp(constants, part.substring(i + 1), [], char, undefined, undefined, {}, depth + 1); // At line 480 (template literal), also pass depth + 1: const skip = restOfExp(constants, part.substring(i + 2), [], '{', undefined, undefined, {}, depth + 1); } ``` Similarly, add depth tracking to `lispify` and `lispifyExpr`: ```typescript function lispify( constants: IConstants, part: CodeString, expected?: readonly string[], lispTree?: Lisp, topLevel = false, depth: number = 0, // ADD depth parameter ): Lisp { if (depth > MAX_PARSE_DEPTH) { throw new ParseError('Expression nesting depth exceeded', part.toString()); } // ... pass depth + 1 to recursive lispify/lispifyExpr calls ... } ```

Related

Alias · CVE (1)

  • CVE-2026-34211 SandboxJS is a JavaScript sandboxing library. vulnerability

Weakness · CWE (1)

  • CWE-674 Uncontrolled Recursion weakness

References