Live › vulnerability › VND-nodejs-january-2026-dos-mitigation-async-hooks
Go to dashboardWhen user code recurses until the stack is exhausted while async_hooks is enabled, Node.js exits immediately with code 7 instead of throwing a catchable RangeError. An attacker who can send deeply nested input to an application that uses AsyncLocalStorage or APM hooks can crash the whole server.
Summary written from the sources below. Check the originals before acting.
First seen by InfraStack 2026-10-23 19:13 UTC. All times on this page are UTC.
Applications where attacker-controlled input drives deep recursion and async_hooks is enabled, which the advisory says is common in React Server Components, Next.js, and most APM setups. Servers that do not use async_hooks or AsyncLocalStorage, or that never recurse on untrusted input, are less exposed.
| Product | Ecosystem | Affected | Fixed in |
|---|---|---|---|
| node | node | – | – |
| node | node | 8.x | – |
| node | node | 18.x | – |
| node | node | 20.x | – |
| node | node | 22.x | – |
| node | node | 24.x | – |
| node | node | 25.x | – |
node --version and compare against the fixed releases listed in the Node.js January 2026 security advisory.dd-trace, newrelic, OpenTelemetry, or uses AsyncLocalStorage or async_hooks.createHook (for example via React Server Components or Next.js).Each row is the first time that source carried this signal. The analyst’s rows are its own findings.
Commercial support for versions past the Maintenance LTS phase is available through our OpenJS Ecosystem Sustainability Program partners Mitigating Denial-of-Service Vulnerability from Unrecoverable Stack Space Exhaustion for React, Next.js, and APM Users MC JC Matteo Collina, Joyee Cheung Mitigating Denial-of-Service Vulnerability from Unrecoverable Stack Space Exhaustion for React, Next.js, and APM Users TL;DR Node.js/V8 makes a best-effort attempt to recover from stack space exhaustion with a catchable error, which frameworks have come to rely on for service availability. An edge case that reproduces only when async_hooks are enabled breaks this recovery path: when recursion in user code exhausts stack space, Node.js exits immediately with exit code 7 instead of throwing a recoverable error. This can be reproduced in countless applications because: React Server Components use AsyncLocalStorage Next.js uses AsyncLocalStorage for request context tracking Other frameworks may also use AsyncLocalStorage for request context tracking Most APM tools (Datadog, New Relic, Dynatrace, Elastic APM, OpenTelemetry) use AsyncLocalStorage or async_hooks.createHook to trace requests The weakness ultimately lies in the ecosystem's reliance on an unspecified behavior in the language - recovery from stack space exhaustion - for service availability ( CWE-758 ). Given the widespread use of async_hooks by popular frameworks and APM tools, the aforementioned edge case can expose this weakness more frequently and can present a Denial‑of‑Service vector for many applications. Node.js shipped a mitigation in the January 2026 security release to make this unspecified behavior more consistent, reducing the chance of reproduction. However, the weakness remains in the ecosystem until applications and frameworks move away from relying on unspecified behavior for availability. For users of these frameworks/tools and server hosting providers : Update as soon as possible. For libraries and frameworks : apply more robust defenses against stack space exhaustion to ensure service availability (e.g., limit recursion depth or avoid recursion if the depth can be controlled by an attacker). A recoverable RangeError: Maximum call stack size exceeded is only an unspecified behavior maintained with best-effort, and cannot be depended on for security guarantees. The Reproduction When a stack overflow occurs in user code while async_hooks is enabled, Node.js immediately exits with code 7 instead of allowing try-catch blocks to catch the error. This is a special condition in Node.js that skips the process.on('uncaughtException') handlers, making the exception uncatchable. import { createHook } from ' node:async_hooks ' ; // This simulates what APM tools do createHook ( { init () {} } ) . enable () ; function recursive () { new Promise ( () => {} ) ; // Creates async context return recursive () ; } try { recursive () ; } catch ( err ) { console . log ( ' This never runs ' , err ) ; } JavaScript Copy to clipboard Expected : try-catch catches the RangeError Actual : Immediate crash with exit code 7 Why This Affects React and Next.js React Server Components React 18+ uses AsyncLocalStorage (which is built on async_hooks ) to track the rendering context for Server Components: // Inside React's internals import { AsyncLocalStorage } from ' node:async_hooks ' ; const asyncLocalStorage = new AsyncLocalStorage () ; // Every server component render creates async context async function renderServerComponent ( Component , props ) { return asyncLocalStorage . run ( { request : currentRequest }, async () => { return < Component {... props } />; } ) ; } JavaScript Copy to clipboard Next.js Request Context Next.js uses AsyncLocalStorage to track request context, cookies, headers, and more: // Simplified from Next.js internals import { AsyncLocalStorage } from ' node:async_hooks ' ; export const requestAsyncStorage = new AsyncLocalStorage () ; // Every request creates async context export function handleRequest ( req , res ) { return requestAsyncStorage . run ( { req , res }, async () => { // Your page/API handler runs here } ) ; } JavaScript Copy to clipboard The Real-World Scenario Consider a Next.js API route that processes user-submitted JSON: // pages/api/process.js export default async function handler ( req , res ) { try { const data = req . body ; const result = processNestedData ( data ) ; // Deeply nested = stack overflow res . json ( { success : true , result } ) ; } catch ( err ) { // THIS CATCH BLOCK NEVER RUNS console . error ( ' Processing failed: ' , err ) ; res . status ( 500 ) . json ( { error : ' Processing failed ' } ) ; } } function processNestedData ( data ) { if ( Array . isArray ( data )) { return data . map ( item => processNestedData ( item )) ; } return transform ( data ) ; } JavaScript Copy to clipboard A user sending deeply nested JSON can crash your entire server: [[[[[[[[[[[[[[[[[[[[[[[[ /* 50,000 levels deep */ ]]]]]]]]]]]]]]]]]]]]]]]] Without async_hooks : try-catch catches the RangeError , returns 500, server continues With async_hooks (React/Next.js) : Server crashes immediately with exit code 7 Why the Use of APM Tools Makes It Easier to Reproduce Application Performance Monitoring (APM) tools are essential infrastructure for production applications. They track request latency, identify bottlenecks, trace errors to their source, and alert teams when something goes wrong. Companies use APM tools like Datadog, New Relic, Dynatrace, Elastic APM, and OpenTelemetry to maintain visibility into their distributed systems. To provide this functionality, APM tools need to follow a request as it flows through your application, even across async boundaries. When an HTTP request comes in, is processed by middleware, queries a database, calls an external API, and finally returns a response, the APM needs to correlate all of these operations into a single trace. This requires async context tracking. Most modern APM tools use AsyncLocalStorage (which is built on async_hooks in versions of Node.js before Node 24) to propagate trace context across async operations. The moment you require('dd-trace') , require('newrelic') , or initialize OpenTelemetry, your application has async_hooks enabled. The irony is notable: the tools you install to monitor and debug crashes can make a category of crashes behave differently. This is not the fault of the APM tools; they are using Node.js APIs exactly as intended. Why This Is Only a Mitigation, and The Vulnerability Lies Elsewhere While this issue has significant practical impact, we want to be clear about why Node.js is treating this fix as a mere mitigation of security vulnerability risks at large: Stack Space Exhaustion Behavior Is Not Specified The "Maximum call stack size exceeded" error is not part of the ECMAScript specification. The specification does not impose any limit and assumes infinite stack space . Imposing a limit and throwing a recoverable error are only behaviors that JavaScript engines implement with best-effort. Applications and frameworks are already at risk when they build a security model on top of these unspecified behaviors that are not guaranteed to reproduce consistently, see: CWE-758: Reliance on Undefined, Unspecified, or Implementation-Defined Behavior CWE-674: Uncontrolled Recursion It's worth noting that even when ECMAScript specifies that proper tail calls should reuse stack frames , this is not implemented by most JavaScript engines today, including V8. And in the few JavaScript engines that do implement it, proper tail calls (as used in the reproduction above ) can block an application with infinite recursion instead of hitting the stack size limit at some point and stopping with an error, which is another Denial-of-Service vector. This reinforces that stack overflow behavior cannot be re
This page as .md · .json · in your agent: get_signal("VND-nodejs-january-2026-dos-mitigation-async-hooks")