权限模型
Deno 权限模型(核心安全特性)
Deno 默认在安全的沙盒环境中执行任何代码——无论是你的主程序还是第三方依赖,都没有文件、网络、环境变量或子进程的访问权限。只有通过 命令行标志显式授予 的权限才能被使用。
一、权限标志速查表
| 权限标志 | 说明 |
|---|---|
--allow-read | 允许读取文件系统。可以限定路径:--allow-read=/etc,/tmp |
--allow-write | 允许写入文件系统。同样可限定写入目录 |
--allow-net | 允许网络访问。可限定域名或 IP:--allow-net=api.example.com,localhost |
--allow-env | 允许读取环境变量(默认无法访问 process.env) |
--allow-run | 允许运行子进程(现应使用 new Deno.Command();Deno.run() 已在 Deno 2 中软移除,仅保留实现、类型已删除) |
--allow-ffi | 允许调用外部函数接口(Foreign Function Interface) |
-A 或 --allow-all | 授予所有权限(仅在开发 / 测试原型时使用,生产环境不推荐) |
--deny-*标志:在已--allow-*的前提下,可进一步拒绝特定路径或域名;deny优先于allow。例如:bashdeno run --allow-net --deny-net=github.com,jsr.io script.ts详见 Permissions。
二、常用命令示例
# 允许读取文件(无限制) + 网络访问
deno run --allow-read --allow-net server.ts
# 限制文件读取路径仅 /data 目录,网络仅允许访问 api.example.com
deno run --allow-read=/data --allow-net=api.example.com main.ts
# 授予所有权限(快速原型,非生产)
deno run -A main.ts
三、动态权限请求(运行时)
可以用 Deno.permissions.request() API 在代码中询问用户是否授予某个权限:
// 请求网络权限
const status = await Deno.permissions.request({ name: "net" });
if (status.state === "granted") {
await fetch("https://deno.land");
} else {
console.error("网络权限未授予");
}
其他动作包括 query(查询当前状态)、revoke(撤销权限)。这个机制适合交互式 CLI 工具,让用户按需授权。
四、安全模型的要点
- 同等对待主程序和依赖:Deno 不区分你的代码和第三方包,两者共享同一个权限集。换句话说,如果一个依赖被授予了
--allow-net,它同样可以发起网络请求。 - 最小权限原则:建议只添加程序确实需要的标志,并使用路径/域名限制,避免使用
-A。 - 无法动态提升权限:
Deno.permissions.request只能降级或询问已在命令行声明范围的权限(例如--allow-net开启了网络权限但未限定域名,则请求时会弹出确认框;如果根本没有--allow-net,则请求会被直接拒绝)。 - 生产部署:通常直接将必要的权限标志写入 Dockerfile 或启动脚本中,不依赖动态弹窗。
五、精细化权限:路径与域名限定
粗粒度的 --allow-read/--allow-net 已经比 Node 默认全放开安全得多,但更推荐进一步限定范围:
# 只允许读取 ./config 与 ./data 两个目录
deno run --allow-read=./config,./data main.ts
# 只允许访问指定的 API 域名,禁止访问其他任意网址
deno run --allow-net=api.example.com,cdn.example.com main.ts
# 同时使用 allow 与 deny:允许所有网络,但明确拒绝内网地址段
deno run --allow-net --deny-net=169.254.169.254 fetch-external.ts
最后一个例子在防止 SSRF(服务端请求伪造)攻击时很实用:即使代码存在漏洞被诱导访问任意 URL,也无法访问云厂商的内网元数据服务。
六、测试中的权限控制
deno test 同样遵循权限模型,测试文件默认无权限,需要什么权限就显式声明,这能帮助及早发现测试对外部资源的隐式依赖:
deno test --allow-net=localhost:8080 --allow-env=NODE_ENV
也可以在测试代码中用 Deno.test 的 permissions 选项为单个测试单独声明所需权限,避免整份测试文件的权限范围被无意放大:
Deno.test({
name: "读取配置文件",
permissions: { read: ["./fixtures/config.json"] },
fn() {
// 仅在此测试内生效的只读权限
},
});
七、与 Node.js 权限模型的对比
| 维度 | Deno | Node.js |
|---|---|---|
| 默认权限 | 默认全部拒绝 | 默认进程级全部允许 |
| 第三方依赖 | 与主程序共享同一权限集,可被限制 | 依赖天然拥有与主程序相同的能力,无原生隔离 |
| 授权方式 | 命令行标志 / 运行时动态请求 | 无内建机制,需依赖第三方沙箱库(如 vm2,且历史上出现过逃逸漏洞) |
| 适合场景 | 运行不完全信任的第三方代码、插件系统 | 传统全信任的服务端应用 |
这也是为什么 Deno 常被用于运行用户提交的脚本(在线代码练习平台、Serverless 函数网关等)场景——权限模型天然提供了一层沙箱。
常见坑
- 以为
--deny-net能单独生效:deny只在对应的allow已经放开的前提下“做减法”,如果本身没有--allow-net,网络请求依旧会被直接拒绝,不需要也不应该额外加deny。 - 开发环境长期使用
-A导致习惯性带入生产:本地为了方便加了-A,上线脚本直接复制粘贴,权限收紧的收益就完全丧失了。 - 以为权限是按“模块”而不是按“进程”授予:所有代码(主程序 + 全部依赖)共享同一份权限集合,无法只给某个第三方包单独限权(除非拆分为独立子进程)。
- 忘记
--allow-env也可以限定具体变量名:--allow-env=PATH,HOME比无限制的--allow-env更安全,很多人不知道该标志同样支持逗号分隔的白名单。
最佳实践
- 遵循最小权限原则:先不加任何权限运行,根据报错逐一补充需要的
--allow-*,而不是一开始就-A。 - 涉及网络、文件的标志尽量加上具体的域名/路径限定,而不是完全开放。
- 面向不受信任代码(插件、用户脚本)的场景,考虑结合
Deno.Command/子进程 + 更严格的权限组合,把风险隔离到独立进程。 - 在 CI 与生产 Dockerfile 中把最终确定的权限标志固化下来,作为该服务“需要哪些资源”的活文档。
延伸阅读
- Deno 基础:权限模型是 Deno 与 Node 最核心的差异点之一。
- 安装与环境:安装完成后即可开始体验权限提示。
- 项目初始化与基础命令:在
deno.json的tasks中固化常用权限组合。
八、总结
Deno 的安全模型是其与 Node.js 最显著的差异之一。默认拒绝,显式授权的设计迫使开发者思考程序的真实需求,从而避免无意中引入安全漏洞。建议新人从 --allow-read、--allow-net 等标志开始,逐步熟悉权限粒度控制。
参考文献
| 资料 | 说明 |
|---|---|
| Security | 官方权限说明 |
| Permissions API | Deno.permissions |
| setup-deno | CI 中的权限与安装 |
相关文章
Deno 基础
Deno 是基于 V8 与 Rust 的 JavaScript/TypeScript 运行时,由 Node.js 原作者 Ryan Dahl 发起。官方文档:docs.deno.com。与 Node、Bun 的对比见 运行时对比。
内置工具链
Deno 提供了开箱即用的工具链,无需安装任何第三方依赖即可完成代码格式化、检查、测试、覆盖率、文档生成和基准测试等任务。
项目初始化与基础命令
Deno 提供了开箱即用的项目管理工具集,无需额外配置即可初始化项目、运行脚本、执行代码片段等。
包管理与依赖
Deno 以 deno.json 管理项目(imports、tasks 等),依赖通过 URL、jsr:、npm: 解析并缓存到本地(默认 DENO_DIR),通常不需要像 Node 那样维护庞大的 node_modules 树。在需…
其他配置
Deno 默认禁止访问环境变量,以遵循最小权限原则。必须通过 --allow-env 标志显式开启。
安装与环境
Deno 提供了多种安装方式,覆盖主流操作系统。安装后需确保环境 PATH 正确,并可配置一些环境变量以控制行为。
Series
deno
1 / 8