Chapter 03

权限安全系统

Deno 最独特的设计——沙盒权限模型,让代码在最小权限下安全运行

为什么需要权限系统

想象你执行了 npm install some-package,然后 node app.js。此时这个 some-package 可以:

而你完全不知情。这在 npm 生态中已经发生过多次供应链攻击事件。

Deno 的权限系统从根本上解决了这个问题:默认沙盒,一切皆需授权

权限标志完整参考

权限标志控制范围细化示例
--allow-read文件系统读取--allow-read=/tmp,./config
--allow-write文件系统写入--allow-write=/tmp
--allow-net网络访问(TCP/UDP)--allow-net=api.example.com:443
--allow-env环境变量读写--allow-env=PORT,DATABASE_URL
--allow-run执行子进程--allow-run=git,npm
--allow-ffi调用动态库(FFI)--allow-ffi=./libcustom.so
--allow-hrtime高精度计时(防时序攻击)无法细化
--allow-sys系统信息(os/cpus/memory)--allow-sys=hostname
--allow-import从 URL 导入模块(默认允许标准域)--allow-import=deno.land
--allow-all开放所有权限(等同 -A)生产环境慎用

细粒度权限控制

--allow-read:限制可读路径

# 仅允许读取 /tmp 和 ./config 目录
deno run --allow-read=/tmp,./config app.ts

# 尝试读取其他路径会报错:
# PermissionDenied: Requires read access to "/etc/passwd",
# run again with the --allow-read flag

--allow-net:限制可访问的主机

# 仅允许访问指定域名和端口
deno run --allow-net=api.stripe.com:443,db.internal:5432 app.ts

# 允许所有网络(不推荐)
deno run --allow-net app.ts

# Deno 2 支持通配符:
deno run --allow-net=*.internal.com app.ts

--allow-env:限制可读取的环境变量

# 仅允许读取指定环境变量
deno run --allow-env=PORT,DATABASE_URL,JWT_SECRET app.ts

// 代码中读取
const port = Deno.env.get("PORT") ?? "8000";
// 尝试读取未授权的变量会返回 undefined(非报错)

--allow-all 的生产风险

慎用 --allow-all(-A)deno run -A app.ts 等同于关闭了所有安全保护,与运行 Node.js 程序无异。在生产环境,你应该始终使用最小权限原则——只授予程序实际需要的权限。--allow-all 只适合开发调试时快速验证。

# 开发环境(方便调试,允许一切)
deno run -A --watch main.ts

# 生产环境(精确控制权限)
deno run \
  --allow-net=0.0.0.0:8000,db.prod:5432 \
  --allow-env=DATABASE_URL,JWT_SECRET,PORT \
  --allow-read=/app/static \
  main.ts

交互式权限提示

当你不加任何权限标志运行代码,Deno 会在程序尝试访问受限资源时弹出交互式提示:

$ deno run hello.ts

┌ ⚠️  Deno requests net access to "api.github.com".
├ Requested by `fetch()` on line 3.
├ Run again with --allow-net to bypass this prompt.
└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all net) >

选项说明:

deno.json 中配置权限

Deno 2 支持在 deno.jsontasks 中为不同任务配置不同权限,也可以通过 permissions 字段(deno 2.1+ 支持)设置默认权限:

// deno.json
{
  "tasks": {
    "start": "deno run --allow-net=0.0.0.0:8000 --allow-env=PORT,DB_URL main.ts",
    "dev":   "deno run -A --watch main.ts",
    "test":  "deno test --allow-read=./fixtures --allow-net=localhost"
  }
}

运行时权限查询

程序可以在运行时检查自己拥有哪些权限:

// 查询权限状态
const netPerm = await Deno.permissions.query({ name: "net", host: "api.example.com" });
console.log(netPerm.state); // "granted" | "denied" | "prompt"

// 主动申请权限(会弹出交互提示)
const result = await Deno.permissions.request({ name: "read", path: "/tmp" });
if (result.state === "granted") {
  // 用户已授权
}

// 主动撤销权限
await Deno.permissions.revoke({ name: "read", path: "/tmp" });

实战:最小权限 HTTP 服务器

下面是一个仅开放必要权限的生产级 HTTP 服务器示例:

// server.ts — 最小权限设计

// 启动命令:
// deno run --allow-net=0.0.0.0:8000 --allow-env=PORT --allow-read=./static server.ts

const PORT = Number(Deno.env.get("PORT")) || 8000;

Deno.serve({ port: PORT }, async (req: Request): Promise<Response> => {
  const url = new URL(req.url);

  // 静态文件服务(仅允许 ./static 目录)
  if (url.pathname.startsWith("/static/")) {
    const filePath = `./static${url.pathname.slice(7)}`;
    try {
      const file = await Deno.readFile(filePath);
      return new Response(file);
    } catch {
      return new Response("Not Found", { status: 404 });
    }
  }

  // API 端点
  if (url.pathname === "/api/health") {
    return Response.json({ status: "ok", time: new Date().toISOString() });
  }

  return new Response("Hello from Deno!");
});

console.log(`Server running on http://localhost:${PORT}`);

本章小结:Deno 的权限系统是其最核心的安全特性。所有权限默认关闭,程序只能在明确授权的范围内操作。通过细化权限(--allow-net=hostname--allow-read=/path),可以实现最小权限原则,大幅降低供应链攻击风险。在 deno.json 的 tasks 中预设权限,是生产环境的最佳实践。