CVE-2025-59466
This Vulnerability has been fixed in the Never-Ending Support (NES) version offered by HeroDevs.
Overview
Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine. It uses an event-driven, non-blocking I/O model that makes it lightweight and efficient for building scalable server-side applications.
An uncaught exception vulnerability (CVE-2025-59466) has been identified in Node.js, which allows deep recursion to terminate the process instead of raising a catchable error, resulting in a Denial of Service. Once async_hooks.createHook() is enabled, the "Maximum call stack size exceeded" RangeError that V8 raises on stack exhaustion never reaches a surrounding try/catch block or a process.on('uncaughtException') handler. The process exits unrecoverably instead, so applications built on AsyncLocalStorage or async_hooks can be crashed by any input that drives them into deep recursion. The issue is tracked as GHSA-52xj-vx8w-46qj.
Per MITRE, CWE-248 (Uncaught Exception) is the weakness where "an exception is thrown from a function, but it is not caught," and "when an exception is not caught, it may cause the program to crash or expose sensitive information." Here the crash is the impact: a single unhandled stack overflow ends the worker process, which is a Denial of Service.
This issue affects the 8.x through 18.x Node.js release lines, which are End-of-Life and receive no upstream fix, as well as the 20.x, 22.x, 24.x, and 25.x lines before 20.20.0, 22.22.0, 24.13.0, and 25.3.0 respectively.
Details
Module Info
- Product: Node.js
- Affected packages: node
- Affected versions: >=8.0.0 <19.0.0; >=20.0.0 <20.20.0; >=22.0.0 <22.22.0; >=24.0.0 <24.13.0; >=25.0.0 <25.3.0
- GitHub repository: https://github.com/nodejs/node
- Package manager: Not applicable. Node.js is distributed as a runtime release rather than a registry package.
- Published packages: https://nodejs.org/en/download
- Fixed in: NES for Node.js v18.20.14 (January 22, 2026), v16.20.9 (January 23, 2026), v14.21.8 (January 27, 2026), and v12.22.14 (January 30, 2026). The four supported release lines shipped the fix on different dates, so there is no single NES fix date. Upstream, the fix shipped in Node.js 20.20.0, 22.22.0, 24.13.0, and 25.3.0.
Vulnerability Info
This High-severity vulnerability is found in the Node.js runtime itself, in the interaction between the async_hooks subsystem and the V8 stack overflow error path. It is present in every published version of the 8.x through 18.x release lines.
The async_hooks module provides an API to track asynchronous resources, and async_hooks.createHook() registers functions to be called for the lifetime events of each async operation. AsyncLocalStorage, the stable API most applications use for request-scoped context, is built on the same machinery and enables a hook internally, so a large share of instrumented services, loggers, and tracing agents have hooks active in production. Separately, when JavaScript recursion exhausts the stack, V8 raises an ordinary RangeError whose message is "Maximum call stack size exceeded". Because it is an ordinary error, a try/catch block can catch it, and if nothing does, the process.on('uncaughtException') handler runs before the process exits.
The two behaviors do not compose. async_hooks callbacks run inside a scope Node.js marks as fatal, which routes any exception raised while that scope is active straight to the runtime's fatal exception path. When the stack overflow RangeError is raised during a hook callback, it is treated as a fatal internal error rather than re-thrown into JavaScript, so neither a surrounding try/catch nor an uncaughtException handler ever observes it and the process exits with code 7 (kExceptionInFatalExceptionHandler). Any code path an attacker can drive into deep recursion then becomes a way to kill a worker process: a deeply nested request body handed to a recursive parser or validator, a self-referential structure walked by a serializer, or a user-supplied depth parameter. Restart supervisors and uncaughtException handlers, the usual defenses against a single bad request, are exactly what the bug bypasses.
The upstream fix adds an IsStackOverflowError() check and re-throws stack overflow RangeErrors from the fatal scope's destructor instead of calling FatalException, which restores the catchable behavior. The same change is carried in the NES for Node.js releases listed above for the End-of-Life release lines.
Note: applications that never enable async_hooks.createHook() and never use AsyncLocalStorage are not affected; without an active hook the stack overflow error remains catchable.
Note: the Node.js project states that this patch "improves recoverability in one edge case but does not remove the broader risk," that recovery from stack space exhaustion is unspecified best-effort behavior, and that applications should validate input and bound or avoid recursion rather than relying on stack exhaustion behavior. See the Node.js security release announcement for the project's full wording.
Steps to Reproduce
The steps below are taken from the public regression tests added by the fix commit (test/parallel/test-async-hooks-stack-overflow.js and test/parallel/test-async-hooks-stack-overflow-try-catch.js), simplified to run without the Node.js test harness.
- Save the following as
overflow-handler.js. It enables a hook, installs anuncaughtExceptionhandler, and then recurses until the stack is exhausted.
'use strict';
const { createHook } = require('async_hooks');
// Enabling any hook is enough; AsyncLocalStorage enables one internally.
createHook({ init() {} }).enable();
process.on('uncaughtException', (err) => {
console.log('handler reached:', err.name, err.message);
});
function recursive() {
new Promise(() => {}); // triggers the async_hooks init callback
return recursive();
}
setImmediate(recursive);- Run it with an affected Node.js version:
node overflow-handler.js
echo "exit code: $?"- On an affected version the handler never runs and the process exits with code 7. On a patched version the handler runs, "handler reached: RangeError Maximum call stack size exceeded" is printed, and the process exits with code 0.
- The same gap applies to ordinary try/catch. Save the following as
overflow-trycatch.jsand run it the same way:
'use strict';
const { createHook } = require('async_hooks');
createHook({ init() {} }).enable();
function recursive(depth = 0) {
new Promise(() => {});
return recursive(depth + 1);
}
try {
recursive();
console.log('no error thrown');
} catch (err) {
console.log('caught:', err.name, err.message);
}- On an affected version the process dies before the catch block is reached. On a patched version it prints "caught: RangeError Maximum call stack size exceeded" and exits 0.
Mitigation
The Node.js 8.x through 18.x release lines are past their End-of-Life date and will not receive an upstream fix for this issue. For more information see here.
Users of the affected components should apply one of the following mitigations:
- Upgrade to a supported Node.js release line that carries the fix (22.22.0, 24.13.0, or 25.3.0 and later).
- Migrate affected applications away from the End-of-Life Node.js release lines.
- Leverage a commercial support partner like HeroDevs for post-EOL security support.
Credits
- Andrew MacPherson (AndrewMohawk) (finder)
- aaron_vercel (reporter)
- Matteo Collina (mcollina) from the Node.js project (remediation developer)