写过 Node.js 的人都以为 npm 包装了就完事了——今天这件事被 Deno 2.0 的权限沙箱从根上翻了

写过 Node.js 的人都以为 npm 包装了就完事了——今天这件事被 Deno 2.0 的权限沙箱从根上翻了

一个 npm 包从安装到在你的服务器上跑起来,中间到底做了什么?大多数人觉得这个问题的答案就是「它跑了我写的代码」。但事实是,它同时跑了这个包自己的代码,还有这个包直接和间接依赖的几百个包——而 Node.js 对这整套代码体系,默认放开了文件系统和网络的全部访问权限。

这就是 Deno 2.0 的创造者 Ryan Dahl 当年做 Node.js 时埋下的最大遗憾。2018 年他自己在 JSConf EU 上公开承认:Node.js 对安全太不讲究了,他后来做的 Deno 就是来纠正这个问题的。2026 年的 Deno 2.0 正式版,终于把这件事做到了生产可用的程度。


Node.js 的权限现状:一个库可以悄悄读取你服务器上任何文件

在 Node.js 环境里,任何一个 npm 包安装之后,只要它想,就可以:

  • 读取 /etc/passwd、你的 .env 文件、SSH 私钥
  • 向任意外部服务器发起网络连接,传出数据
  • 启动子进程执行任意命令

这不是假设,而是已经发生过多起真实供应链攻击:恶意包在正常依赖里潜伏,收集环境变量或外传密钥,然后等待时机成熟时触发。Node.js 的 npm install 不会给你任何提示,代码跑起来之前你也几乎没有办法提前知道一个包到底会访问什么。

这不是 npm 生态的问题,而是 Node.js 本身没有提供任何机制去约束运行时权限。

Deno 2.0 的解法:沙箱默认关闭,权限按需申请

Deno 2.0 的核心安全模型是「默认拒绝,按需授权」。代码跑在一个真正的沙箱里,没有你的明确许可,任何文件操作、网络访问、环境变量读取都会被直接拒绝。

# 这个命令只能访问 github.com 和 deno.land 两个域名
deno run --allow-net=github.com,deno.land fetch.ts

# 只能读取当前目录和配置目录
deno run --allow-read=./,./config app.ts

# 只能访问数据库相关的两个环境变量
deno run --allow-env=DATABASE_URL,REDIS_URL server.ts

即使是第三方包里写了恶意代码想偷偷往境外传数据,只要你没有给它 --allow-net 权限,沙箱直接拦截,根本没有机会出网。

细粒度控制:精确到域名和目录路径

Deno 的权限不是「全有或全无」。网络权限可以精确到 IP 地址和端口,文件权限可以精确到目录层级。

# 允许访问 Stripe API(只读)和本地服务(只写)
deno run 
  --allow-net=api.stripe.com:443,127.0.0.1:8000 
  --allow-read=./config 
  --allow-write=/tmp/uploads 
  --allow-env=DATABASE_URL 
  server.ts

即使是同一个 --allow-net,也可以同时设定允许和拒绝的列表,Deny 优先于 Allow。企业级场景可以在 CI/CD pipeline 里对这些权限做审计,把权限清单提交到 Pull Request 里做 Code Review——这是以前根本做不到的事。

--allow-all 存在,但它是明确的决策,不是默认

Deno 提供了 --allow-all-A)选项来禁用所有沙箱限制,但它的存在是显式的。在开发环境里快速迭代可以加这个 flag,部署到生产环境时,谁加了这个 flag、为什么加,全部在 git 历史里可查。

相比之下,Node.js 的「默认全开」从来不是一个决策,它只是一个没人注意到的默认行为。

供应链安全的实际意义

最近几年 npm 生态里的供应链攻击越来越频繁。攻击者不只是直接发布恶意包,更多是通过维护一个看似无害的常用库,等待它被广泛依赖后再植入恶意代码。这类攻击的可怕之处在于:它在被发现的之前几乎没有痕迹。

Deno 的权限沙箱在这里提供了一道独立的安全边界。即使某天你的某个依赖被攻破了,恶意代码最多只能访问你在权限清单里明确授权的那些资源。它没法读你服务器上其他目录的文件,没法把数据发到你授权范围之外的地方。

Deno 2.0 还引入了权限审计日志功能。设置 DENO_AUDIT_PERMISSIONS=/path/to/audit.jsonl,每次权限访问都会记录到文件里,包含时间戳、权限类型和访问的目标地址。配合 OpenTelemetry 可以直接接入现有的可观测性系统,在异常权限访问发生时触发告警。

从 Node.js 迁移:Deno 2.0 现在可以跑你的整个项目

Deno 2.0 最大的变化是正式支持了 npm 和 Node.js 的向后兼容。现在你不需要重写任何东西,可以直接在一个 Deno 进程里跑现有的 Express 应用和几乎所有的 npm 包。

// 这个 Express 应用在 Node.js 和 Deno 2.0 里都能跑
import express from "npm:express";
import { z } from "npm:zod";
import chalk from "npm:chalk";

const app = express();
app.get("/", (req, res) => {
  res.json({ message: "Hello from Deno 2.0!" });
});
app.listen(3000, () => console.log("Server running"));

但迁移的价值不只是「能跑」。当你把应用跑在 Deno 沙箱里,你就有了在 Node.js 里根本不可能实现的安全管控能力。第三方包的权限被严格隔离,生产部署时的攻击面大幅缩小。


简单说:Deno 2.0 让「npm 包能访问什么」这件事从「装完就知道了」变成了「跑之前你就定好了」。这个模型在理论上早就应该成为服务端 JS 的标配,实践了八年终于到了生产可用的节点。

如果你的团队现在还在用纯 Node.js 做服务端,跑一个 Deno 2.0 沙箱对照一下现有的权限使用情况,你会发现很多包访问的资源比它实际需要的多得多——而这件事在 Node.js 里从来没人知道。

评论区

0 条评论

登录后可评论。

铁锈·Rust工具链 16 阅读