Deno 入门教程 / 第 05 章:权限系统
第 05 章:权限系统
5.1 为什么需要权限系统?
在 Node.js 中,任何脚本一旦运行就拥有完整的系统权限。这意味着:
npm install some-package
→ some-package 的 postinstall 脚本可以:
├── 读取 ~/.ssh/id_rsa(私钥)
├── 读取 ~/.aws/credentials(云凭证)
├── 访问所有环境变量
├── 连接任意网络地址(数据外泄)
└── 删除任意文件
Deno 的权限系统从设计层面解决了这个问题——默认没有任何权限。
deno run script.ts
→ script.ts 默认只能:
├── 执行纯计算(数学、字符串处理等)
├── 使用标准 JavaScript API
└── 控制台输出(console.log)
→ 任何需要系统资源的操作必须显式授权
5.2 权限标志详解
完整权限标志列表
| 标志 | 缩写 | 说明 | 可限制范围 |
|---|---|---|---|
--allow-read | -r | 文件系统读取 | 指定路径列表 |
--allow-write | -w | 文件系统写入 | 指定路径列表 |
--allow-net | -n | 网络访问 | 指定主机/端口 |
--allow-env | -e | 环境变量访问 | 指定变量名 |
--allow-run | 无 | 子进程执行 | 指定命令 |
--allow-ffi | 无 | 动态库加载 | 指定路径 |
--allow-sys | 无 | 系统信息查询 | 指定 API |
--allow-all | -A | 所有权限 | 无 |
读写权限 (--allow-read / --allow-write)
# 允许读取所有文件
deno run --allow-read script.ts
# 允许读取特定目录
deno run --allow-read=/tmp,/home/user/data script.ts
# 允许写入特定文件
deno run --allow-write=/tmp/output.txt script.ts
# 组合使用
deno run --allow-read=/etc --allow-write=/tmp script.ts
// 精细权限演示
// 运行:deno run --allow-read=/tmp --allow-write=/tmp demo.ts
// ✅ 允许:读取 /tmp 下的文件
const data = await Deno.readTextFile("/tmp/test.txt");
// ❌ 拒绝:读取 /etc 下的文件
try {
await Deno.readTextFile("/etc/hosts");
} catch (e) {
console.error("权限不足:", e.message);
}
// ✅ 允许:写入 /tmp 下的文件
await Deno.writeTextFile("/tmp/output.txt", "Hello");
网络权限 (--allow-net)
# 允许所有网络访问
deno run --allow-net script.ts
# 允许特定主机
deno run --allow-net=api.github.com,deno.land script.ts
# 允许特定端口
deno run --allow-net=0.0.0.0:8000 script.ts
# 允许主机+端口组合
deno run --allow-net=api.example.com:443,localhost:3000 script.ts
// 运行:deno run --allow-net=api.github.com script.ts
// ✅ 允许:访问 GitHub API
const res1 = await fetch("https://api.github.com/users/denoland");
console.log("GitHub:", res1.status);
// ❌ 拒绝:访问其他域名
try {
const res2 = await fetch("https://api.example.com/data");
} catch (e) {
console.error("网络权限不足:", e.message);
}
环境变量权限 (--allow-env)
# 允许所有环境变量
deno run --allow-env script.ts
# 允许特定变量
deno run --allow-env=HOME,PATH,DB_HOST script.ts
// 运行:deno run --allow-env=HOME,USER script.ts
// ✅ 允许:读取 HOME
console.log("HOME:", Deno.env.get("HOME"));
// ❌ 拒绝:读取 DB_PASSWORD
try {
console.log("DB:", Deno.env.get("DB_PASSWORD"));
} catch (e) {
console.error("环境变量权限不足:", e.message);
}
子进程权限 (--allow-run)
# 允许运行 git 命令
deno run --allow-run=git script.ts
# 允许运行多个命令
deno run --allow-run=git,npm,ls script.ts
// 运行:deno run --allow-run=ls script.ts
// ✅ 允许:运行 ls 命令
const process = new Deno.Command("ls", { args: ["-la"] });
const output = await process.output();
console.log(new TextDecoder().decode(output.stdout));
// ❌ 拒绝:运行 rm 命令
try {
const rm = new Deno.Command("rm", { args: ["/tmp/test"] });
await rm.output();
} catch (e) {
console.error("子进程权限不足:", e.message);
}
系统信息权限 (--allow-sys)
# 允许获取系统信息
deno run --allow-sys script.ts
# 允许特定 API
deno run --allow-sys=osRelease,memoryUsage script.ts
// 运行:deno run --allow-sys script.ts
console.log("操作系统:", Deno.osRelease());
console.log("内存使用:", Deno.memoryUsage());
console.log("系统架构:", Deno.build.arch);
外部函数接口权限 (--allow-ffi)
# 允许加载动态库
deno run --allow-ffi=/usr/lib/libsqlite3.so script.ts
// 运行:deno run --allow-ffi script.ts
const dylib = Deno.dlopen("/usr/lib/libsqlite3.so", {
"sqlite3_libversion": { parameters: [], result: "pointer" },
});
5.3 权限检查 API
Deno 提供了运行时 API 来检查权限状态:
// 检查当前权限状态
const readPerm = await Deno.permissions.query({ name: "read", path: "/tmp" });
console.log("读取 /tmp 权限:", readPerm.state);
// "granted" | "denied" | "prompt"
// 检查网络权限
const netPerm = await Deno.permissions.query({ name: "net", host: "example.com" });
console.log("访问 example.com 权限:", netPerm.state);
// 检查环境变量权限
const envPerm = await Deno.permissions.query({ name: "env", variable: "HOME" });
console.log("读取 HOME 权限:", envPerm.state);
运行时请求权限
// 运行时动态请求权限(会弹出交互提示)
async function readFile(path: string): Promise<string | null> {
// 先检查
const status = await Deno.permissions.query({ name: "read", path });
if (status.state === "granted") {
return await Deno.readTextFile(path);
}
if (status.state === "prompt") {
// 请求权限
const result = await Deno.permissions.request({ name: "read", path });
if (result.state === "granted") {
return await Deno.readTextFile(path);
}
}
return null;
}
const content = await readFile("/tmp/data.txt");
if (content) {
console.log(content);
} else {
console.error("无法读取文件:权限不足");
}
撤销权限
// 运行时撤销已授予的权限
await Deno.permissions.revoke({ name: "read", path: "/tmp" });
// 之后再访问 /tmp 将被拒绝
try {
await Deno.readTextFile("/tmp/test.txt");
} catch (e) {
console.error("权限已被撤销:", e.message);
}
5.4 权限配置文件
在 deno.json 中配置权限
// deno.json
{
"tasks": {
"dev": "deno run --watch --allow-net --allow-read=./data server.ts",
"test": "deno test --allow-read --allow-write",
"start": "deno run --allow-net --allow-env server.ts"
}
}
权限配置最佳实践
// deno.json — 生产级配置
{
"tasks": {
"dev": "deno run --watch --allow-net=localhost:8000,api.example.com --allow-read=./src --allow-env=PORT,NODE_ENV server.ts",
"test": "deno test --allow-read=./testdata --allow-write=./testdata/tmp",
"lint": "deno lint",
"fmt": "deno fmt"
}
}
5.5 安全模型实践
场景 1:Web 服务器最小权限
// 只需要网络权限和少量环境变量
{
"tasks": {
"serve": "deno run --allow-net=0.0.0.0:8000 --allow-env=PORT,HOST server.ts"
}
}
场景 2:文件处理脚本
{
"tasks": {
"process": "deno run --allow-read=./input --allow-write=./output process.ts"
}
}
场景 3:数据库操作
{
"tasks": {
"db": "deno run --allow-net=localhost:5432 --allow-env=DB_URL --allow-read=./migrations db.ts"
}
}
场景 4:自动化脚本(受信环境)
{
"tasks": {
"script": "deno run --allow-all script.ts"
}
}
⚠️ 警告:
--allow-all跳过所有安全检查,只在受信环境中使用。
5.6 权限与 npm 包
使用 npm 包时要特别注意权限:
// npm 包可能需要额外权限
import express from "npm:express@4.18";
const app = express();
app.get("/", (req, res) => res.send("Hello"));
// 需要网络权限
app.listen(3000);
# npm 包可能暗中读写文件、访问网络
# 建议先用最小权限测试,逐步放宽
deno run --allow-net=localhost:3000 app.ts
审计 npm 包权限需求
# 查看模块依赖图
deno info npm:express@4.18
# 查看具体文件列表
deno info --json npm:express@4.18 | jq '.modules'
5.7 权限系统原理
┌─────────────────────────────────────────┐
│ 你的代码 │
│ await Deno.readTextFile("/etc/hosts") │
├─────────────────────────────────────────┤
│ Deno Runtime API 层 │
│ 1. 解析调用参数 │
│ 2. 检查权限状态 │
│ 3a. granted → 执行操作 │
│ 3b. prompt → 弹出交互提示 │
│ 3c. denied → 抛出 PermissionDenied │
├─────────────────────────────────────────┤
│ Rust 核心层 │
│ 实际执行系统调用 │
└─────────────────────────────────────────┘
每个需要权限的 Deno API 调用都会经过权限检查器。检查器维护一个权限表:
// 权限表(运行时内部)
PermissionTable = {
read: [{ granted: true, path: "/tmp" }],
write: [{ granted: true, path: "/tmp" }],
net: [{ granted: true, host: "api.example.com" }],
env: [{ granted: true, variable: "HOME" }],
// 未列出的权限默认 denied
}
5.8 本章小结
| 要点 | 说明 |
|---|---|
| 默认安全 | Deno 默认没有任何系统权限 |
| 精细控制 | 每个权限都可以限制到具体资源 |
| 运行时 API | 可以动态查询、请求和撤销权限 |
| 最佳实践 | 始终使用最小必要权限 |
| npm 兼容 | npm 包也需要遵守权限系统 |
权限标志速查表
deno run \
--allow-read=/tmp,/data \ # 文件读取
--allow-write=/tmp \ # 文件写入
--allow-net=api.example.com \ # 网络访问
--allow-env=DB_HOST,DB_PORT \ # 环境变量
--allow-run=git \ # 子进程
--allow-sys \ # 系统信息
script.ts
📖 扩展阅读
下一章:第 06 章:模块系统 → 了解 Deno 的 URL 导入、npm 兼容与导入映射。