返回资讯中心
外部精选
软件工程
#社区热议

开发者批评 Deno 权限系统安全性名不副实

来源标题:I'm not a fan of Deno's security model (2024)

一位开发者撰文认为 Deno 的权限系统存在根本性缺陷,宣称的「默认安全」具有误导性。他指出 Deno 的权限机制依赖标准库拦截对外访问,但无法真正隔离 JS 代码的越权行为。Deno 官网将权限系统列为旗舰特性,支持启动时指定 --allow-read、--allow-run 等权限,并可在运行时申请权限。

Hacker Newsmaxloh2026/11/8 · 5 天前27 分钟阅读

以下正文由 AI 翻译自 Hacker News(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。

这个问题困扰我有一阵子了,所以我想把想法写下来,好从脑子里清出去,然后继续往前走。我们要聊的是 Deno 处理安全的方式,特别是它的权限系统,以及为什么我认为这种做法从根本上就不安全,而且有误导性。

Deno 是一个 JavaScript 语言运行时。它包含标准库、包管理器、TypeScript 转译器,还有一堆跟我们这个故事无关的东西。它不是 JavaScript 实现,所以它不解释也不编译 JavaScript,那部分它复用 V8。

Deno 的旗舰功能之一就是它的权限系统(在他们主页上挂在“Secure by default”标题下面)。思路很简单:因为跑在 Deno 里的 JS 代码需要标准库来访问外部世界,Deno 就可以选择允许或禁止哪些访问,所以当你运行

shelldeno run --allow-read=/tmp/dir --allow-run=uname code.ts

标准库就只会允许 code.ts 读 /tmp/dir 和运行 uname。

另外,程序可以在运行时请求权限,所以像这样的代码

typescriptconst r = { name: "read", path: "/tmp/dir" } as const; await Deno.permissions.request(r);

会在终端里显示一个挺好看的小提示:

┌ ⚠️ Deno requests read access to "/tmp/dir". ├ Requested by Deno.permissions.request() API. ├ Run again with --allow-read to bypass this prompt. └ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions) >

然后你可以批准或拒绝它。

(顺便说一句:我真的很怀念浏览器扩展里有这个功能。现在,如果你的扩展需要潜在的访问任何网站的权限,它就得请求对所有网站的实际访问权限,这不太理想)

如果你喜欢解谜,这里就是你可以停下来想想这个系统会出什么问题的地方。

我想指出的两个具体问题里,这个是较轻的那个。我们先讨论它,因为这是我注意到的第一件事,而且我最初读到这个的时候,它确实立刻让我不爽。(如果你只对更劲爆的部分感兴趣,可以直接跳到下一节。)

那么,这个挺好看的小提示是显示在终端里的。它不是那种 PolKit 的 GUI 窗口之类的,它只是 stdout 上的文本,等着从 stdin 读输入。这做法太野了。

你看,终端的 API 有点怪。应用能发给终端的大多数命令/请求都是带内的,通过打印到 stdout 来发送。然后终端可以通过往 stdin 发数据来回复。由于 Deno 允许程序往 stdout 打印任意数据、从 stdin 读任意数据,而不需要任何权限也不做任何过滤,恶意代码就可以给终端发任意命令。这通常没问题,因为终端不会因为 stdout 让它执行 shell 命令它就执行,你本来应该能随便 cat 文件而不用冒任意代码执行的风险。但如果你允许恶意代码对终端为所欲为,然后又在同一个终端上打印提示,那就明显不太行了。

你可以,比如说,通过搞乱终端颜色来把提示藏起来。(这个在 2026 年被关掉了,因为权限提示是“部分可见”的(你能看到一个 emoji),而且“AI agent 倾向于”改终端颜色,所以那是个“有效用例”)或者骗用户预先往 stdin 里塞批准。或者骗终端把批准作为对某个请求的回复塞进 stdin。(终端永远不会往 stdin 发回车来避免执行命令,但“按回车继续”太常见了,不会让人起疑;用户根本看不到真正的提示。)

这里有个 asciicast,我觉得看起来挺酷的。

那个录屏里的具体问题早就修好了;终端颜色的问题没有。那其实不太重要,因为真正的问题是:把安全敏感信息放在攻击者控制的通道上。这就像在网页上执行攻击者控制的 JS 代码,然后在同一个页面上要密码。你没法知道页面处于安全状态,所以你没法信任任何输入。

即使 Deno 设法修好了“标准”转义码的每一个问题,你永远不知道外面还存在哪些非标准码。很多终端都有一些,你没法预测它们会怎么跟你的提示交互。

(顺便说一句:那 sudo 怎么就能做到呢?区别在于 sudo 是先问密码,在运行任何其他代码之前。要那样骗 sudo,你得在还没输入 sudo 之前就搞坏终端状态,而且得安静到你打字时注意不到。如果某个终端允许这样,我会认为那是终端的 bug)

那么,我上面写的那个命令有个怪东西(我复制到这里供参考):

shelldeno run --allow-read=/tmp/dir --allow-run=uname code.ts

--allow-run=uname 到底是什么意思?程序又不是有规范名字,不是 org.gnu.coreutils.uname,甚至不是 /nix/store/8k1dmzr3gwzqdgw45nzj76902ca7g1is-coreutils-full-9.5/bin/uname。它只是个快捷方式,意思是“逐个查看 PATH 里的所有目录,看能不能在那里找到 uname”。

你可以给子进程设置环境变量而不需要任何额外权限,那如果我们这样呢

$ cat t.ts const command = new Deno.Command("uname", { // my current working directory, can be anything env: { 'PATH': '/tmp/tmp.7ofmKaNW83' }, }); const { code, stdout, stderr } = command.outputSync(); console.log(new TextDecoder().decode(stdout)); $ cat uname #!/bin/sh printf lol $ deno run --allow-run=uname t.ts lol

是啊。

又一次,我们有了一个漂亮又方便的接口,但从安全角度看它根本说不通。如果程序能靠名字唯一标识就好了,但它们不能,而假装它们能就会导致安全漏洞。

好笑的是,我第一次折腾 Deno 的时候,甚至没想到会有这个问题,

(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →