安全与权限
Deno 默认是安全的。除非您明确启用,否则使用 Deno 运行的程序无权访问敏感 API,例如文件系统访问、网络连接或环境访问。您必须通过命令行标志或运行时权限提示明确授予对这些资源的访问权限。这与 Node 有着主要区别,在 Node 中,依赖项会自动获得对所有系统 I/O 的完全访问权限,这可能为您的项目引入隐藏的漏洞。
在使用 Deno 运行完全不可信的代码之前,请阅读下面的关于执行不可信代码的部分。
关键原则 跳至标题
在深入了解权限的细节之前,了解 Deno 安全模型的关键原则至关重要。
- 默认无 I/O 访问权限:在 Deno 运行时中执行的代码无法读取或写入文件系统上的任意文件,无法发出网络请求或打开网络监听器,无法访问环境变量,也无法生成子进程。
- 对同等权限级别代码执行无限制:Deno 允许通过多种方式执行任何代码(JS/TS/Wasm),包括
eval
、new Function
、动态导入和 Web Worker,这些代码具有相同的权限级别,并且对其来源(网络、npm、JSR 等)的限制很少。 - 同一应用程序的多次调用可以共享数据:Deno 提供了一种机制,允许同一应用程序的多次调用通过内置缓存和 KV 存储 API 共享数据。不同的应用程序无法查看彼此的数据。
- 在同一线程上执行的所有代码共享相同的权限级别:在同一线程上执行的所有代码共享相同的权限级别。不同模块在同一线程中拥有不同权限级别是不可能的。
- 未经用户同意,代码无法提升其权限:在 Deno 运行时中执行的代码未经用户通过交互式提示或调用时标志明确同意,无法提升其权限。
- 初始静态模块图可以无限制地导入本地文件:初始静态模块图中导入的所有文件都可以无限制地导入,即使没有为该文件明确授予读取权限。这不适用于任何动态模块导入。
这些关键原则旨在提供一个环境,用户可以在其中执行代码,同时最大限度地降低对主机或网络造成损害的风险。安全模型旨在易于理解,并在运行时和其中执行的代码之间提供清晰的关注点分离。安全模型由 Deno 运行时强制执行,不依赖于底层操作系统。
权限 跳至标题
默认情况下,对大多数系统 I/O 的访问是被拒绝的。即使在默认情况下,也有一些 I/O 操作以有限的能力被允许。这些将在下面描述。
要启用这些操作,用户必须明确授予 Deno 运行时权限。这是通过向 deno
命令传递 --allow-read
、--allow-write
、--allow-net
、--allow-env
和 --allow-run
标志来完成的。
在脚本执行期间,当运行时提示时,用户还可以明确授予对特定文件、目录、网络地址、环境变量和子进程的权限。如果标准输出/标准错误不是 TTY,或者向 deno
命令传递了 --no-prompt
标志,则不会显示提示。
用户还可以通过使用 --deny-read
、--deny-write
、--deny-net
、--deny-env
和 --deny-run
标志来明确禁止访问特定资源。这些标志优先于允许标志。例如,如果您允许网络访问但拒绝访问特定域,则拒绝标志将优先。
Deno 还提供了一个 --allow-all
标志,该标志授予脚本所有权限。这会完全禁用安全沙箱,应谨慎使用。--allow-all
具有与在 Node.js 中运行脚本相同的安全属性(即没有)。
定义:-A, --allow-all
deno run -A script.ts
deno run --allow-all script.ts
默认情况下,Deno 不会为权限请求生成堆栈跟踪,因为它会影响性能。用户可以使用 DENO_TRACE_PERMISSIONS
环境变量启用堆栈跟踪。
文件系统访问 跳至标题
默认情况下,执行代码无法读取或写入文件系统上的任意文件。这包括列出目录内容、检查给定文件是否存在以及打开或连接 Unix 套接字。
读取文件的访问权限通过 --allow-read
(或 -R
)标志授予,写入文件的访问权限通过 --allow-write
(或 -W
)标志授予。这些标志可以指定一个路径列表,以允许访问特定文件或目录及其中的任何子目录。
定义:--allow-read[=<PATH>...]
或 -R[=<PATH>...]
# Allow all reads from file system
deno run -R script.ts
# or
deno run --allow-read script.ts
# Allow reads from file foo.txt and bar.txt only
deno run --allow-read=foo.txt,bar.txt script.ts
# Allow reads from any file in any subdirectory of ./node_modules
deno run --allow-read=node_modules script.ts
定义:--deny-read[=<PATH>...]
# Allow reading files in /etc but disallow reading /etc/hosts
deno run --allow-read=/etc --deny-read=/etc/hosts script.ts
# Deny all read access to disk, disabling permission prompts for reads.
deno run --deny-read script.ts
定义:--allow-write[=<PATH>...]
或 -W[=<PATH>...]
# Allow all writes to file system
deno run -W script.ts
# or
deno run --allow-write script.ts
# Allow writes to file foo.txt and bar.txt only
deno run --allow-write=foo.txt,bar.txt script.ts
定义:--deny-write[=<PATH>...]
# Allow reading files in current working directory
# but disallow writing to ./secrets directory.
deno run --allow-write=./ --deny-write=./secrets script.ts
# Deny all write access to disk, disabling permission prompts.
deno run --deny-write script.ts
Deno 中的某些 API 在底层使用文件系统操作实现,即使它们不提供对特定文件的直接读/写访问。这些 API 读取和写入磁盘,但不需要任何明确的读/写权限。这些 API 的一些示例如下:
localStorage
- Deno KV
caches
Blob
由于这些 API 是使用文件系统操作实现的,因此用户可以使用它们来消耗文件系统资源(如存储空间),即使他们没有直接访问文件系统的权限。
在模块加载期间,Deno 可以从磁盘加载文件。这有时需要明确的权限,有时默认允许:
- 默认情况下,所有以可静态分析方式从入口模块导入的文件都允许读取。这包括静态
import
语句和参数为指向特定文件或文件目录的字符串字面量的动态import()
调用。此列表中的所有文件都可以使用deno info <entrypoint>
打印出来。 - 以无法静态分析的方式动态导入的文件需要运行时读取权限。
node_modules/
目录中的文件默认允许读取。
从网络获取模块或将 TypeScript 代码转译为 JavaScript 时,Deno 会使用文件系统作为缓存。这意味着即使用户没有明确授予读/写权限,Deno 也可以消耗文件系统资源(如存储空间)。
网络访问 跳至标题
默认情况下,执行代码无法发出网络请求、打开网络监听器或执行 DNS 解析。这包括发出 HTTP 请求、打开 TCP/UDP 套接字以及监听 TCP 或 UDP 上的传入连接。
网络访问权限通过 --allow-net
标志授予。此标志可以指定一个 IP 地址或主机名列表,以允许访问特定的网络地址。
定义:--allow-net[=<IP_OR_HOSTNAME>...]
或 -N[=<IP_OR_HOSTNAME>...]
# Allow network access
deno run -N script.ts
# or
deno run --allow-net script.ts
# Allow network access to github.com and jsr.io
deno run --allow-net=github.com,jsr.io script.ts
# A hostname at port 80:
deno run --allow-net=example.com:80 script.ts
# An IPv4 address on port 443
deno run --allow-net=1.1.1.1:443 script.ts
# An IPv6 address, all ports allowed
deno run --allow-net=[2606:4700:4700::1111] script.ts
定义:--deny-net[=<IP_OR_HOSTNAME>...]
# Allow access to network, but deny access
# to github.com and jsr.io
deno run --allow-net --deny-net=github.com,jsr.io script.ts
# Deny all network access, disabling permission prompts.
deno run --deny-net script.ts
在模块加载期间,Deno 可以从网络加载模块。默认情况下,Deno 允许从以下位置使用静态和动态导入加载模块,而无需明确的网络访问权限:
https://deno.land/
https://jsr.deno.org.cn/
https://esm.sh/
https://raw.githubusercontent.com
https://gist.githubusercontent.com
这些位置是受信任的“公共利益”注册表,预计不会通过 URL 路径启用数据泄露。您可以使用 --allow-imports
标志添加更多受信任的注册表。
此外,Deno 允许通过 npm:
说明符导入任何 NPM 包。
Deno 还会每天最多一次向 https://dl.deno.land/
发送请求,以检查 Deno CLI 的更新。这可以通过设置 DENO_NO_UPDATE_CHECK=1
环境变量来禁用。
环境变量 跳至标题
默认情况下,执行代码无法读取或写入环境变量。这包括读取环境变量和设置新值。
对环境变量的访问权限通过 --allow-env
标志授予。此标志可以指定一个环境变量列表,以允许访问特定的环境变量。从 Deno v2.1 开始,您现在可以指定后缀通配符,以允许对环境变量进行“范围限定”的访问。
定义:--allow-env[=<VARIABLE_NAME>...]
或 -E[=<VARIABLE_NAME>...]
# Allow access to all environment variables
deno run -E script.ts
# or
deno run --allow-env script.ts
# Allow HOME and FOO environment variables
deno run --allow-env=HOME,FOO script.ts
# Allow access to all environment variables starting with AWS_
deno run --allow-env="AWS_*" script.ts
定义:--deny-env[=<VARIABLE_NAME>...]
# Allow all environment variables except
# AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.
deno run \
--allow-env \
--deny-env=AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY \
script.ts
# Deny all access to env variables, disabling permission prompts.
deno run --deny-env script.ts
Windows 用户注意:环境变量在 Windows 上不区分大小写,因此 Deno 也(仅在 Windows 上)不区分大小写地匹配它们。
Deno 在启动时会读取某些环境变量,例如 DENO_DIR
和 NO_COLOR
(查看完整列表)。
NO_COLOR
环境变量的值对在 Deno 运行时中运行的所有代码都可见,无论代码是否被授予读取环境变量的权限。
系统信息 跳至标题
默认情况下,执行代码无法访问系统信息,例如操作系统版本、系统正常运行时间、平均负载、网络接口和系统内存信息。
对系统信息的访问权限通过 --allow-sys
标志授予。此标志可以指定一个允许的接口列表,来自以下列表:hostname
、osRelease
、osUptime
、loadavg
、networkInterfaces
、systemMemoryInfo
、uid
和 gid
。这些字符串映射到 Deno
命名空间中提供 OS 信息的函数,例如 Deno.systemMemoryInfo
。
定义:--allow-sys[=<API_NAME>...]
或 -S[=<API_NAME>...]
# Allow all system information APIs
deno run -S script.ts
# or
deno run --allow-sys script.ts
# Allow systemMemoryInfo and osRelease APIs
deno run --allow-sys="systemMemoryInfo,osRelease" script.ts
定义:--deny-sys[=<API_NAME>...]
# Allow accessing all system information but "networkInterfaces"
deno run --allow-sys --deny-sys="networkInterfaces" script.ts
# Deny all access to system information, disabling permission prompts.
deno run --deny-sys script.ts
子进程 跳至标题
在 Deno 运行时中执行的代码默认无法生成子进程,因为这违反了未经用户同意代码无法提升其权限的原则。
Deno 提供了一种执行子进程的机制,但这需要用户明确的权限。这通过使用 --allow-run
标志来完成。
您从程序中生成的任何子进程都独立于授予父进程的权限运行。这意味着子进程可以访问系统资源,而不管您授予生成它的 Deno 进程的权限。这通常被称为特权升级。
因此,请确保您仔细考虑是否要授予程序 --allow-run
访问权限:它实质上使 Deno 安全沙箱失效。如果您确实需要生成特定的可执行文件,您可以通过向 --allow-run
标志传递特定的可执行文件名称来限制 Deno 进程可以启动的程序,从而降低风险。
定义:--allow-run[=<PROGRAM_NAME>...]
# Allow running all subprocesses
deno run --allow-run script.ts
# Allow running "curl" and "whoami" subprocesses
deno run --allow-run="curl,whoami" script.ts
您可能永远不希望使用 --allow-run=deno
,除非父进程具有 --allow-all
,因为能够生成 deno
进程意味着脚本可以生成另一个具有完全权限的 deno
进程。
定义:--deny-run[=<PROGRAM_NAME>...]
# Allow running running all programs, but "whoami" and "ps".
deno run --allow-run --deny-run="whoami,ps" script.ts
# Deny all access for spawning subprocessing, disabling
# permission prompts.
deno run --deny-run script.ts
默认情况下,npm
包在安装期间(例如使用 deno install
)不会执行其安装后脚本,因为这会允许任意代码执行。当使用 --allow-scripts
标志运行时,npm 包的安装后脚本将作为子进程执行。
FFI(外部函数接口) 跳至标题
Deno 提供了一种FFI 机制,用于在 Deno 运行时内执行用其他语言编写的代码,例如 Rust、C 或 C++。这通过使用 Deno.dlopen
API 完成,该 API 可以加载共享库并调用其中的函数。
默认情况下,执行代码不能使用 Deno.dlopen
API,因为这违反了未经用户同意代码无法提升其权限的原则。
除了 Deno.dlopen
之外,FFI 还可以通过 Node-API (NAPI) 原生插件使用。这些也默认不被允许。
Deno.dlopen
和 NAPI 原生插件都需要使用 --allow-ffi
标志明确授予权限。此标志可以指定一个文件或目录列表,以允许访问特定的动态库。
与子进程类似,动态库不在沙箱中运行,因此它们没有与加载它们的 Deno 进程相同的安全限制。因此,请极其谨慎地使用。
定义:--allow-ffi[=<PATH>...]
# Allow loading dynamic all libraries
deno run --allow-ffi script.ts
# Allow loading dynamic libraries from a specific path
deno run --allow-ffi=./libfoo.so script.ts
定义:--deny-ffi[=<PATH>...]
# Allow loading all dynamic libraries, but ./libfoo.so
deno run --allow-ffi --deny-ffi=./libfoo.so script.ts
# Deny loading all dynamic libraries, disabling permission prompts.
deno run --deny-ffi script.ts
从 Web 导入 跳至标题
允许从 Web 导入代码。默认情况下,Deno 限制了您可以从中导入代码的主机。这对于静态导入和动态导入都适用。
如果您想动态导入代码,无论是使用 import()
还是 new Worker()
API,都需要授予额外的权限。从本地文件系统导入需要 --allow-read
,但 Deno 也允许从 http:
和 https:
URL 导入。在这种情况下,您需要指定一个明确的 --allow-import
标志。
# allow importing code from `https://example.com`
$ deno run --allow-import=example.com main.ts
默认情况下,Deno 允许从以下主机导入源:
deno.land
esm.sh
jsr.io
cdn.jsdelivr.net
raw.githubusercontent.com
gist.githubusercontent.com
只允许使用 HTTPS 导入。
此允许列表默认适用于静态导入,如果指定了 --allow-import
标志,则默认也适用于动态导入。
# allow dynamically importing code from `https://deno.land`
$ deno run --allow-import main.ts
请注意,为 --allow-import
指定允许列表将覆盖默认主机列表。
代码评估 跳至标题
Deno 对同等权限级别的代码执行没有限制。这意味着在 Deno 运行时中执行的代码可以使用 eval
、new Function
,甚至动态导入或 Web Worker 来执行任意代码,其权限级别与调用 eval
、new Function
、动态导入或 Web Worker 的代码相同。
此代码可以托管在网络上,可以是本地文件(如果授予了读取权限),或者作为纯文本字符串存储在调用 eval
、new Function
、动态导入或 Web Worker 的代码中。
执行不可信代码 跳至标题
虽然 Deno 提供了旨在保护主机和网络免受损害的安全功能,但不可信的代码仍然令人担忧。执行不可信代码时,拥有多层防御至关重要。下面概述了一些执行不可信代码的建议,我们建议在执行任意不可信代码时使用所有这些方法:
- 以受限权限运行
deno
,并预先确定实际需要运行的代码(并使用--frozen
锁文件和--cached-only
防止加载更多代码)。 - 使用操作系统提供的沙箱机制,如
chroot
、cgroups
、seccomp
等。 - 使用沙箱环境,如虚拟机(VM)或微虚拟机(MicroVM)(gVisor、Firecracker 等)。