写过 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 里从来没人知道。
评论区
登录后可评论。