当你的 Next.js 应用突然… 死掉时:一直导致服务器崩溃的隐蔽 Stack Overflow Bug

发布: (2026年2月3日 GMT+8 16:13)
9 min read
原文: Dev.to

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 版本是最新的!

Back to Blog

相关文章

阅读更多 »

Koa 的零配置支持

Vercel 现在支持 applications,一个富有表现力的 HTTP 中间件框架,使编写 Web 应用和 API 更加愉快,且无需配置。Koa…

什么是混合开发者?

什么是Hybrid Developer?在当今快速发展的技术世界中,Hybrid Developer是一名精通多种技术或平台的软件开发人员,能够……