Deno 权限模型详解:默认最小信任的设计

要点

开山篇(Deno 2 入门)交代了定位与安装,本篇拆开 Deno 与 Node 最根本的差异——权限模型(permission model):进程默认没有任何敏感能力,读写文件、访问网络、读环境变量、起子进程,每一项都要显式授权。本文写作时稳定线是 Deno 2.5.x(2025-09-10 发布的 LTS 线),2.5 刚好把权限声明补进了 deno.json(permission sets),这块拼图到这一版才算完整。看完能回答四个问题:默认锁住的能力面有多大、怎么把授权收敛到最小、进程跑起来之后怎么动态查与收权限、以及这套沙箱救不了什么。

威胁模型:防的主要是依赖

先说清它防什么。Node 里进程能力全开,一段被投毒的依赖(postinstall 脚本、传递依赖里的恶意补丁)可以静默读走 .env、扫内网、把数据 POST 出去——npm 生命周期脚本默认拦截是 Deno 2.0 才对齐的事,而运行期能力在 Node 里永远没有开关。Deno 把这条攻击面挪进了运行时:代码能做什么不由代码自己决定,由启动命令决定。同样的恶意代码在 Deno 默认配置下第一次碰文件系统就抛 Deno.errors.NotCapable,攻击链第一步就断。

代价是每个脚本都要回答一遍”我到底需要什么”。这正是设计意图:权限声明就是能力清单,评审时一眼能看出这个程序配不配碰这些资源。

能力面与授权标志

默认拒绝的能力面共八类,外加一个总开关。短标志自 1.46(2024-06)起可用:

能力 授权标志 短标志 作用域写法示例
读文件 –allow-read -R –allow-read=./data,./config
写文件 –allow-write -W –allow-write=./tmp
网络 –allow-net -N –allow-net=api.github.com:443
环境变量 –allow-env -E –allow-env=API_KEY,DATABASE_URL
子进程 –allow-run — –allow-run=git,curl
系统信息 –allow-sys -S –allow-sys=hostname,osUptime
原生库(FFI) –allow-ffi — –allow-ffi=./libnative.so
动态导入 –allow-import — –allow-import=cdn.example.com
全部 -A / –allow-all — 等价于关掉沙箱

三个容易漏的语义:

其一,静态导入不需要权限。模块加载期(import 语句、字面量 import())不在沙箱管辖内——import 本地文件不用 –allow-read,拉远程模块不用 –allow-net。–allow-import 管的是运行时动态 import 非字面量路径这类场景;远程模块的默认信任名单是 deno.land、jsr.io、esm.sh、cdn.jsdelivr.net、raw.githubusercontent.com 等官方源,显式指定列表会覆盖默认值。

其二,net 的作用域是主机粒度:api.github.com 不含子域,要子域用 *.example.com 通配;加端口用 host:port 写法。

其三,env 支持后缀通配(2.1+):–allow-env=”AWS_*” 放行一组变量,比整类放行干净得多。

# 一个最小 web 服务的完整授权:能听本机 8000 端口、能读 ./static、只认 PORT 一个环境变量
deno run --allow-net=127.0.0.1:8000 --allow-read=./static --allow-env=PORT server.ts

1. 短标志等价写法
deno run -N=127.0.0.1:8000 -R=./static -E=PORT server.ts

跑起来之后:查询、请求、回收

权限不是只有命令行一个入口,运行时有一组协商 API。典型用法是能力探测后降级,而不是权限缺失直接崩:

// query:不发提示,只问状态。有缓存目录就落盘,没有就退化到内存——降级而非崩溃
const status = await Deno.permissions.query({ name: "write", path: "./cache" });
if (status.state === "granted") {
  await Deno.writeTextFile("./cache/last-run.txt", new Date().toISOString());
} else {
  console.log("无写权限,跳过缓存");
}

// request:向用户要权限,交互终端会弹授权提示;拒绝或非交互环境拿到 prompt 状态
const req = await Deno.permissions.request({ name: "env", variable: "API_KEY" });

被拒的操作抛 Deno.errors.NotCapable,可以 catch 后走降级路径。这与站内Node.js 错误处理的思路一致:可预期的失败要接住并分类,只是这里的”失败”是能力缺失而非数据错误。revoke 则把已授的权限降回提示态,长生命周期进程可以”用完即收”。

写进配置:deno.json 与 permission sets

命令行标志的弱点是散落——每个人本地的启动命令都不一样,CI 里又抄一份,漂移迟早发生。2.0 起权限可以写进 deno.json 的任务定义,2.5 补上最后一块:命名权限集(permission sets),把权限声明成可复用的组:

{
  "permissions": {
    "default": {
      "read": ["./data"],
      "env": true
    },
    "pull": {
      "read": ["./data"],
      "write": ["./data"],
      "net": ["hacker-news.firebaseio.com:443"],
      "env": ["WEBHOOK_URL"]
    }
  },
  "tasks": {
    "pull": "deno run -P=pull pull.ts"
  }
}

名为 default 的集合裸 -P 即可启用,其余用 -P=名字指名调用。权限还能嵌在 test、bench、compile 键下分别声明——测试要写临时目录、主程序不用,这就拆开了。2.5 之前这些权限只能挤在 task 的命令字符串里;之后声明归声明、执行归执行,改权限不再需要改命令行习惯。

反模式点名

反模式一:日常一律 -A。教程图省事全带 -A,等于把 Deno 唯一的差异化能力亲手关掉,剩下的是一个速度未必更快、生态肯定更小的 Node。正确姿势是把权限写进 deno.json 的任务与集合,让”授权”进入代码评审。

反模式二:用 –allow-run 把沙箱开洞。子进程是独立程序,带着操作系统全量权限,不在 JS 沙箱管辖内;–allow-run=deno 尤其危险——deno 又能以任意权限再起进程。更阴险的组合是 read 加 write 加 run:能改写 PATH 里的二进制,等它下次自己跑。授权前先问:这个进程真的需要调外部命令吗?

反模式三:把 deny 当摆设。–deny-* 优先级高于 –allow-*,–allow-read –deny-read=/etc 是”全盘可读但 /etc 例外”;而裸 –deny-read(不带作用域)会把整个读权限连同交互提示一起焊死。两种写法语义不同,混用前想清楚要哪一种。

审计与调试

“为什么被拒””被谁用了”都有现成工具:DENO_TRACE_PERMISSIONS=1 让每次权限检查打印调用栈,定位是哪行代码在要权限;2.5 起加了 DENO_AUDIT_PERMISSIONS=文件路径,把全部授权事件以 JSONL 落盘(可与 tracing 叠加),给无人值守进程留审计记录。非交互环境记得配 –no-prompt:CI 里弹权限提示没人点”允许”,进程只会挂住——不如让它直接失败,报错比挂死好查。

边界:这套沙箱救不了什么

诚实交代:权限模型护的是 JS 层发起的系统调用。–allow-ffi 加载的原生库直接做系统调用,绕开一切检查;被 –allow-run 放出去的子进程同理;运行时引擎自身的漏洞也不在模型覆盖内。所以它是纵深防御的一层,不是全部——对外暴露的服务该有的容器隔离、非 root 运行、网络分区一样不能少。另外它约束的是”这份代码”,不约束”人”:能改启动命令的人就能授出任意权限,生产环境的启动命令应当进版本控制(deno.json 的任务正是干这个的)。

延伸阅读

开山篇Deno 2 入门有权限模型的定位叙述与 Node/Bun 三方对比;全系列命令、API 与 deno.json 配置键的速查表见Deno 开发参考字典;把权限收口用进真实工程的完整过程,见实战篇用 Deno 写一个定时抓取脚本。

版本核实(以官方文档为准):默认拒绝的权限模型自 Deno 1.x 起为默认行为;–deny-* 优先于 –allow-*、Deno.errors.NotCapable、Deno.permissions.query/request/revoke、DENO_TRACE_PERMISSIONS 均为长期稳定特性;短标志 -R/-W/-N/-E/-S 自 1.46(2024-06);–allow-env 后缀通配自 2.1;npm 生命周期脚本默认拦截(–allow-scripts)自 2.0;permission sets(deno.json 的 permissions 键、-P 调用、test/bench/compile 嵌套)与 DENO_AUDIT_PERMISSIONS 为 2.5(2025-09-10)新增。远程模块默认信任源名单以 docs.deno.com 当前文档为准。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇