Node.js 服务器的 Brotli 漏洞,升级 npm 没用
Node.js 修复了可由恶意 Brotli 数据触发的高危漏洞。真正要核对的是生产运行时与基础镜像:22.23.4、24.22.1、26.12.1 才是对应修复线。
如果今天只能给线上服务做一件安全事,别先打开 package-lock.json。
进到那台真正跑流量的机器、容器或函数环境里,敲一次 node --version。这次要找的不是某个 npm 包,而是 Node.js 运行时自己带着的 Brotli 解码器。
Node.js 在 10 月 21 日发布的安全更新里修了一个 High 级别漏洞 CVE-2026-111497。官方的描述很克制:应用若用 node:zlib 解压攻击者提供的异常 Brotli 数据,解码器可能越界写入内部缓冲区,后果可能是进程崩溃、内存泄露,或远程代码执行。
报告者一栏写着 OpenAI Codex Security 的 Khai Tran 和 Sylvie Mayer。后来 OpenAI 也公开提到,这个问题是在一次红队演练中发现的。听起来像一条 AI 安全新闻,但值班的人真正该听见的是另一句话:补丁在运行时,不在你的业务依赖树里。
这两天不少同题文章在讲“压缩数据也能出 RCE”。这话没错,不过容易把人带去两个极端:要么把每个 Node 服务都当成立刻失守,要么觉得跑一次 npm update 就已经处理完。
两种都不太对。

把排查焦点从依赖锁文件移到实际运行时:只有外部数据进入 Brotli 解码路径,才需要进一步判断暴露面。
先判断:你的服务有没有走到这条解码路径
这不是一句“所有 Node 项目都有洞”就能说完的事。
Node.js 官方公告给出的条件很具体:攻击者要能提供会被应用解压的 Brotli 数据,路径落在 node:zlib 的 Brotli 解码上。也就是说,真正值得优先看的,是那些会处理外部压缩内容的服务:接收外部请求并主动解压的接口、会拉取不受信任内容的抓取或预览任务、代理与边缘层之间有压缩内容转换的链路。
反过来,只有一个没有相关解码逻辑的 Node 进程,并不等于自动具备这条利用链。别把“可能导致 RCE”读成“每个端口已经被接管”。官方公告没有说已有大规模实际利用;它说的是内存破坏可能进一步导致远程代码执行。这个限定词得留着,安全通告里最怕把未确认的范围写成恐慌片。
但也别因此把它拖到下个迭代。公开独立复现已经说明,未修复的官方 Node.js 24.22.0 镜像在特定配置下可以被恶意 Brotli 数据触发代码执行,而修复版会拒绝同一输入。Imperva 的复现说明 值得安全同学看;业务团队不需要研究利用细节,重点是把受影响的运行时换掉。
为什么 npm 审计可能给你一种错觉
很多团队的默认动作是:漏洞来了,跑 npm audit,升级一个包,绿了就关工单。
这次它可能什么也没告诉你。
package.json 和 lockfile 管的是应用依赖;Node.js 的版本却通常来自 Docker 基础镜像、虚拟机上的系统包、CI runner、Serverless 的运行时选项,或者某个没人注意的构建缓存。你把业务包升级得很漂亮,最终镜像仍可能带着旧的 Node 二进制。
这也是这条漏洞更值得写的地方。它不是又一条“多装一个扫描器”的新闻,而是在提醒我们:生产里的组件边界,不等于仓库里的依赖边界。
一个很常见的失配长这样:本地开发机用的是新版本,CI 缓存里还是旧版本,生产镜像又从更老的 tag 构建。三个地方都说“Node 24”,但小版本不同,安全结论也不同。版本大类写对了,不代表补丁真的进去了。
先把当前版本拿出来,不要凭记忆猜:
# 在实际运行服务所在的环境里执行
node --version
# 需要记录更完整的运行时信息时
node -p 'process.versions'
如果服务跑在容器里,检查最终镜像或正在运行的 Pod;如果跑在函数平台,检查平台当前绑定的运行时版本和重新部署后的版本。检查 CI 的 Node 版本也很重要,但它只能证明构建环境,不等于线上进程已经替换。

版本口径来自 Node.js 2026 年 10 月 21 日安全公告;请以你所用发行版或托管平台的更新说明为准。
版本表比“升级到最新版”更有用
Node.js 这次给了三条受支持分支的修复版本:
| 运行时分支 | 首个修复版本 |
|---|---|
| Node.js 22.x | 22.23.4 |
| Node.js 24.x | 24.22.1 |
| Node.js 26.x | 26.12.1 |
如果你在这三条线里,目标就很清楚:升级到对应版本或更高的同一受支持版本。别只写“升级 Node”,工单里最好把目标版本写死,否则很容易有人把 24.22.0 升成另一个仍低于 24.22.1 的镜像。
Node.js 官方还特别提醒,安全更新发布时,停止支持的版本应视为受影响。这里最容易被遗漏的是“还能跑”的旧服务:它在监控里正常、镜像也多年没变,于是没人碰它。可这类服务往往没有下一次小版本补丁可等,处理动作不是继续找一个不存在的补丁,而是把迁移排进明确的变更计划。
升级的顺序可以简单到有点朴素:先盘点运行时,再升级基础镜像或平台运行时,重新构建和部署,最后回到线上执行一次版本核验。中间的 npm install 可以照常做,但它不是这次修复的终点。
给值班同事留一张小记录
安全处理最怕“大家都以为别人改了”。这次我会给每个服务留四行,不需要写成一篇长报告:
服务/环境:api-prod
实际 Node.js:v24.22.1
镜像或运行时来源:node:24.22.1 / 已部署的镜像摘要
外部 Brotli 解码路径:有/无/待确认;负责人和复核时间
第四行不是表格洁癖。它把最重要的不确定性亮出来:这个服务到底会不会解压外部 Brotli 内容?不知道就写“待确认”,并给它一个负责人。比起在群里说一句“应该没事”,这四行更像一次可交接的修复。
还有个顺手能避免的误会:不要为了临时止血,把所有压缩功能一刀切关掉,然后忘了回头。更新到修复运行时是主线;临时收紧外部内容处理,只应该是评估后、带回滚方案的补充。业务链路、性能与安全不是非黑即白的选择题。
这次漏洞由 AI 安全团队发现,当然很吸睛。但对绝大多数后端团队来说,真正的变化没那么科幻:以后看到“某运行时内置组件出了洞”,第一反应别是搜 npm 包名,而是问——哪一个生产进程正在跑哪个二进制?
你们团队排 Node 安全更新时,最容易漏掉的是基础镜像、CI runner,还是托管运行时?
小岛 AI
记录一线实战、踩坑与成长