denoland/deno
Permissions
Active contributors: Bartek Iwańczuk, David Sherret, Nayeem Rahman
Purpose
Deno's defining feature is that user code runs sandboxed by default. The deno_permissions crate (runtime/permissions/) implements that sandbox: it defines the permission categories, parses CLI --allow-*/--deny-* flags, gates every privileged op, and prompts the user when permission is needed at runtime.
runtime/permissions/lib.rs is the largest single file in the repo at 10,921 lines, and the model it implements has been remarkably stable since v1.0.
Directory layout
runtime/permissions/
├── Cargo.toml # deno_permissions crate
├── lib.rs # 10,921 lines: PermissionsContainer + every category
├── broker.rs # external permission broker (IPC) support
├── ipc_pipe.rs # IPC pipe used by broker.rs
├── prompter.rs # interactive prompt rendering
├── runtime_descriptor_parser.rs # parses --allow-*/--deny-* descriptor strings
└── which.rs # PATH search abstraction (used by run perms)Permission categories
Each category has an enum (UnaryPermission<T> for the parametrized ones), a check_* method on PermissionsContainer, and corresponding --allow-*/--deny-* CLI flags.
| Category | What it gates | Examples |
|---|---|---|
read |
Filesystem reads | Deno.readFile, import "file:..." resolution |
write |
Filesystem writes | Deno.writeFile, Deno.mkdir |
net |
Network access | Deno.connect, fetch, Deno.listen |
env |
Environment variable read/write | Deno.env.get/set |
sys |
System info | Deno.osRelease, Deno.systemMemoryInfo, hostname |
run |
Subprocess execution | Deno.Command, child_process.spawn |
ffi |
Foreign function interface | Deno.dlopen |
import |
Remote imports | import "https://..." |
import_signing |
Trust gate for signed imports | imports with integrity hashes |
Each category supports list semantics: --allow-net=api.example.com,1.2.3.4, --deny-read=/etc, etc. The descriptor parser is in runtime_descriptor_parser.rs.
Key abstractions
| Type | File | Role |
|---|---|---|
PermissionsContainer |
runtime/permissions/lib.rs |
The shared, mutable handle threaded through the runtime |
Permissions |
runtime/permissions/lib.rs |
The inner state (per-category permission sets) |
PermissionState |
runtime/permissions/lib.rs |
Granted / GrantedPartial / Prompt / Denied |
PermissionDeniedError |
runtime/permissions/lib.rs |
JsError of class NotCapable raised when a check fails |
RuntimePermissionDescriptorParser |
runtime/permissions/runtime_descriptor_parser.rs |
Parses descriptors at runtime (for Deno.permissions.request) |
PromptResponse |
runtime/permissions/prompter.rs |
Allow / Deny / AllowAll |
permission_prompt |
runtime/permissions/prompter.rs |
Renders the interactive (y/n/A/d) prompt |
BrokerResponse |
runtime/permissions/broker.rs |
IPC-driven permission decisions |
AUDIT_SINK |
runtime/permissions/lib.rs |
Static sink that logs permission grants/denies (file or OTel) |
How it works
graph TD
CliFlags["--allow-net=foo<br/>--deny-write<br/>…"] --> Parse["RuntimePermissionDescriptorParser"]
Parse --> Container["PermissionsContainer<br/>(constructed in cli/factory.rs)"]
Container --> OpState["op_state.put(container)"]
OpState -->|every op| Op["op_*"]
Op -->|"check_read(&path, 'api')"| Container
Container -->|"granted"| Allow["proceed"]
Container -->|"prompt"| Prompter["permission_prompt"]
Prompter -->|"y"| Allow
Prompter -->|"n"| Deny["PermissionDeniedError"]
Container -->|"denied"| Deny
Container --> Broker["external broker?"]
Broker -->|"yes"| BrokerSock["IPC pipe (broker.rs)"]
BrokerSock --> Allow
Container --> Audit["AUDIT_SINK"]
Audit --> File["log file or OTel"]Check call sites
Every privileged op in ext/* follows the same pattern:
state
.borrow_mut::<PermissionsContainer>()
.check_read(&path, "Deno.openSync()")?;The string is the API name shown to the user in the prompt and in the resulting error. Search for check_read, check_write, check_net, check_env, check_sys, check_run, check_ffi, check_import to find every gate point — there are several hundred.
Prompt flow
When a check hits a permission in the Prompt state:
lib.rscallsprompter::permission_prompt(name, api, is_unary, get_stack).prompter.rsrenders a TTY prompt viadeno_terminal, with optional stacktrace.- The user responds
y(allow once),Y(allow all for this category),n(deny once),N(deny all), orA(allow all categories). - The result is recorded back into the container so subsequent calls don't re-prompt for the same descriptor.
If stdout/stderr aren't TTYs, prompts are auto-denied. The --no-prompt flag forces this behavior.
Broker mode
For sandboxed deployments, an external "permission broker" can be configured via env vars (see broker.rs). When a broker is configured, maybe_check_with_broker sends the permission request over a Unix-domain pipe to the broker process, which makes the decision. This is what Deno Deploy uses to centralize permission policy across many runtime instances.
Audit sink
Permission grants and denies can be logged to either a file or an OpenTelemetry stream via AUDIT_SINK. This is opt-in (off by default) and is used by enterprise deployments to track permission usage.
Integration points
- CLI flag entry:
cli/args/flags.rs(the--allow-*/--deny-*flags) →cli/args/flags_net.rsfor network flag parsing. - Container construction:
cli/factory.rsbuilds thePermissionsContainerfrom parsed flags. - Worker insertion:
runtime/worker.rs::MainWorker::bootstrap_from_optionsandruntime/web_worker.rs::WebWorker::bootstrap_from_optionsinsert the container into op state. - Op call sites: every
ext/<name>/*.rsop that does anything privileged callscheck_*. - JS entry:
Deno.permissions.query/request/revokeare implemented by ops inruntime/ops/permissions.rs(or wherever the runtime ops module re-exports them).
Entry points for modification
- New permission category: add an enum variant to
Permissions, add acheck_<name>method onPermissionsContainer, add--allow-<name>/--deny-<name>flags, add JS-sideDeno.permissions.query("<name>")support, document the category. - New descriptor type for an existing category (e.g., a more granular net descriptor): extend
runtime_descriptor_parser.rsand the matchingUnaryPermissionimpl inlib.rs. - Change the prompt UI: edit
prompter.rs. Note that automated tests rely on the existing prompt format — update spec tests accordingly. - Add an audit log field: extend the
OtelAuditFnsignature and the file-sink writer inlib.rs.
Key source files
| File | Purpose |
|---|---|
runtime/permissions/lib.rs |
The whole permission model: categories, PermissionsContainer, check_* methods, PermissionDeniedError |
runtime/permissions/prompter.rs |
Interactive prompt rendering, PERMISSION_EMOJI, permission_prompt |
runtime/permissions/broker.rs |
External permission broker over IPC (used by Deno Deploy) |
runtime/permissions/ipc_pipe.rs |
The IPC pipe primitive used by broker.rs |
runtime/permissions/runtime_descriptor_parser.rs |
Parses descriptor strings like read=/foo,/bar |
runtime/permissions/which.rs |
PATH search for --allow-run |
cli/args/flags.rs |
The --allow-* / --deny-* flag definitions |
cli/args/flags_net.rs |
Net-permission flag parsing (CIDR ranges, port lists) |
cli/factory.rs |
Constructs the PermissionsContainer for a worker |
For the broader runtime context see Runtime. For how this fits into request flow, see Architecture.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.