当你的 Next.js 应用突然… 死掉时:一直导致服务器崩溃的隐蔽 Stack Overflow Bug
Source: Dev.to
所以,想象一下:你正在开发你的 Next.js 应用,一切运行顺畅,测试通过,代码审查也很满意。你部署到生产环境,结果……砰。服务器直接挂掉。没有错误日志。没有可捕获的异常。只有退出码 7,应用就消失了。
听起来熟悉吗?你并不孤单。这种情况已经困扰全栈开发者好几个月,根源是 Node.js 中一个狡猾的 bug,已在 2026 年 1 月修复。让我们深入了解到底哪里出错,以及如何保护你的应用。
这到底发生了什么?
Node.js 中存在一个关键漏洞(CVE‑2025‑59466),当深度递归遇到一个叫 async_hooks 的小东西时,可能会导致整个应用崩溃。而且最糟糕的是,你的错误处理器根本捕获不到它。
这不仅仅是理论上的 bug,它实际上影响了:
- React Server Components
- Next.js 应用
- 所有主流 APM 工具(Datadog、New Relic、Dynatrace、Elastic APM、OpenTelemetry)
基本上,任何使用 AsyncLocalStorage 的应用——也就是几乎所有构建现代全栈应用的项目。
Source: …
实际问题
你正在使用 Next.js 构建一个典型的全栈应用。也许你在处理用户提交的 JSON,解析嵌套对象,或遍历深层数据结构。这些都是日常操作,对吧?
// 看起来很无害,对吧?
async function processNestedData(data, depth = 0) {
if (depth > 100) return; // 你甚至加了安全检查!
if (typeof data === 'object') {
for (let key in data) {
await processNestedData(data[key], depth + 1);
}
}
return data;
}
现在想象一下,恶意用户(或只是糟糕的数据)给你发送了类似下面的内容:
// 深度嵌套的对象,看起来像:{a: {a: {a: {a: ... }}}}
let evil = {};
let current = evil;
for (let i = 0; i {
try {
causeChaos(100000); // 这会导致栈溢出
console.log('This never prints');
} catch (error) {
console.log('Neither does this'); // Bug: catch block never executes
}
});
- 修补前: 进程立即崩溃,错误未被捕获。
- 修补后: 错误被正确捕获并处理。
Source: …
修复方法(以及如何保护自己)
Node.js 团队已为 20.x、22.x、24.x 和 25.x 版本发布了补丁。即使打了补丁,也不应依赖栈溢出错误处理来保证安全性或可用性。
1. 立即 更新 Node.js
# 检查当前版本
node --version
# 更新到已打补丁的版本:
# v20.x → v20.18.2 或更高
# v22.x → v22.13.2 或更高
# v24.x → v24.0.2 或更高
# v25.x → v25.3.0 或更高
2. 验证输入深度
不要让用户决定递归的深度。加入实际的检查:
function safeProcessData(data, maxDepth = 50) {
function process(obj, currentDepth = 0) {
if (currentDepth > maxDepth) {
throw new Error(`Data nesting exceeds maximum depth of ${maxDepth}`);
}
if (typeof obj !== 'object' || obj === null) {
return obj;
}
// 递归处理对象属性
for (const key in obj) {
obj[key] = process(obj[key], currentDepth + 1);
}
return obj;
}
return process(data);
}
3. 保护异步上下文
如果需要使用 AsyncLocalStorage,仅在真正需要的最小代码块中包装,并在该上下文 之外 进行递归深度检查。
const { AsyncLocalStorage } = require('async_hooks');
const als = new AsyncLocalStorage();
function handleRequest(req, res) {
// 首先验证 / 清理输入
const safeBody = safeProcessData(req.body);
// 然后再运行 async‑local 上下文
als.run(new Map(), () => {
// 依赖 async hooks 的业务逻辑
doSomethingAsync(safeBody).then(...).catch(...);
});
}
4. 监控进程退出码
为 exit 事件添加监听器,以记录异常终止码:
process.on('exit', (code) => {
if (code !== 0) {
console.error(`Process exited with code ${code}`);
// 在此发送警报或执行重启逻辑
}
});
TL;DR
- Bug: 深度递归 +
async_hooks导致 Node.js 以代码 7 退出 (CVE‑2025‑59466)。 - Impact: 使 Next.js、React Server Components 以及所有使用
AsyncLocalStorage的应用崩溃。 - Fix: 将 Node.js 更新到 2026 年 1 月 13 日发布的已修补版本。
- Mitigation: 验证输入深度,限制 async‑hook 的使用,并监控退出代码。
保持安全,及时更新依赖,永远不要再信任未检查的递归! 🚀
1️⃣ 安全递归处理
function safeProcessData(data, maxDepth = 50) {
function process(obj, currentDepth = 0) {
if (currentDepth > maxDepth) {
throw new Error('Maximum depth exceeded');
}
if (Array.isArray(obj)) {
return obj.map(item => process(item, currentDepth + 1));
}
if (obj && typeof obj === 'object') {
const result = {};
for (const key in obj) {
result[key] = process(obj[key], currentDepth + 1);
}
return result;
}
return obj;
}
return process(data);
}
// Use it like this
app.post('/api/data', (req, res) => {
try {
const safeData = safeProcessData(req.body, 50);
// Process safeData…
res.json({ success: true });
} catch (error) {
res.status(400).json({ error: error.message });
}
});
2️⃣ 使用迭代而非递归
在可能的情况下,将方法扁平化:
// Recursive traversal
function recursiveSum(arr) {
if (!Array.isArray(arr)) return arr;
return arr.reduce((sum, item) => sum + recursiveSum(item), 0);
}
// Iterative approach
function iterativeSum(arr) {
const stack = [...arr];
let sum = 0;
while (stack.length) {
const item = stack.pop();
if (Array.isArray(item)) {
stack.push(...item);
} else {
sum += item;
}
}
return sum;
}
3️⃣ 添加请求大小限制
不要让巨大的负载到达你的代码:
const express = require('express');
const app = express();
// Limit JSON payload size
app.use(
express.json({
limit: '100kb',
verify: (req, res, buf, encoding) => {
// Additional validation if needed
if (buf.length > 100000) {
throw new Error('Request too large');
}
},
})
);
4️⃣ 监控你的部署
设置适当的监控,以便捕获崩溃:
// Process exit handlers
process.on('exit', code => {
console.error(`Process exiting with code: ${code}`);
// Log to your monitoring service
});
process.on('uncaughtException', error => {
console.error('Uncaught Exception:', error);
// Send to error tracking (Sentry, etc.)
process.exit(1);
});
🧪 测试你的修复
一个简单的测试,用于验证你的应用已受到保护:
// test/stack-overflow.test.js
const request = require('supertest');
const app = require('../app');
describe('Stack Overflow Protection', () => {
it('should reject deeply nested JSON', async () => {
// Create evil deeply nested object
let evil = { data: 'value' };
let current = evil;
for (let i = 0; i {
const normalData = {
user: {
profile: {
settings: {
theme: 'dark',
},
},
},
};
const response = await request(app)
.post('/api/process')
.send(normalData)
.expect(200);
expect(response.body.success).toBe(true);
});
});
📚 更大的图景
这个 bug 展示了为什么 2026 年的全栈开发如此疯狂:
- 前端 – React、Next.js 实现服务器端渲染
- 后端 – Node.js API 处理用户数据
- 云 – 提供商在不透明的环境中运行代码
- APM – 工具试图追踪一切
当一个微小的部件出错时,整个堆栈可能会崩溃。
关键教训: 不要指望运行时会拯救你。 从一开始就编写防御性代码。
✅ 全栈开发者快速检查清单
- 更新到最新的 Node.js 补丁版本
- 验证所有用户数据的输入深度
- 配置请求大小限制
- 在可能的情况下用迭代替代递归
- 设置错误监控(Sentry、Datadog 等)
- 为退出代码 7 配置警报
- 添加覆盖边缘情况的集成测试
- 使用恶意负载进行负载测试
🛠 这对你的技术栈意味着什么
| 框架 / 平台 | 操作 |
|---|---|
| Next.js | 立即更新 Node.js;async_hooks 被大量使用。 |
| Express / Fastify | 如果使用 AsyncLocalStorage 仍然存在漏洞。请更新并添加输入验证。 |
| NestJS | 同样——更新 Node.js 并审计递归逻辑。 |
| Serverless(Lambda、Vercel 等) | 核实运行时版本;大多数提供商会自动更新,但请再次确认。 |
🏁 最终思考
错误时有发生——即使是像 Node.js 这样经过实战考验的技术。关键是保持信息灵通,主动保护你的应用程序。
- 验证输入。
- 编写防御性代码。
- 测试边缘情况。
- 监控所有内容。
而且,为了 JavaScript 的健康,保持你的 Node.js 版本是最新的!