Permissions & capabilities

esrun grants nothing by default. A service reaches only what the command line that started it named. The check lives on the native operation, not in JavaScript, so importing a built-in never bypasses the gate.

Capabilities

Every host operation declares the capability it requires. A denied capability throws a standard DOMException with the NotAllowedError name.

CapabilityGrants
EnvRead environment, arguments, cwd, platform; backs runtime:process.
FileReadRead files within the configured root jail.
FileWriteWrite files within the configured root jail.
NetOpen outbound network connections: runtime:net connect, UDP send, fetch and WebSocket.
NetListenBind and accept connections: runtime:net, runtime:http and runtime:websocket.
SignalsWatch OS signals through runtime:process.
RunSpawn a child process through runtime:system.
WorkerStart a Worker, whose own grant is narrower.
HrTimeAccess high-resolution timing.

Importing a runtime: module never needs a capability. The gate is the operation, so a built-in imports under any policy and only its operations throw.

Handles belong to one agent

A capability authorizes opening a resource; the handle that comes back belongs to the agent that created it. Sockets, listeners, child processes, file descriptors, HTTP servers and in-flight requests reject use from another agent with ERR_FOREIGN_HANDLE, whether or not that agent holds the capability. MessagePort is the deliberate exception because it is designed to transfer.

Granting capabilities in esrun

There are two modes, each with one direction. They cannot be combined:

Shell
esrun --allow-imports --allow-net app.js    # nothing, plus these
esrun --allow-all --deny-run app.js         # everything, minus these
ModeBaselineDirection
--allow-<name>Nothing granted (the default)Additive only
--allow-all --deny-<name>Everything grantedSubtractive only

--deny-<name> requires --allow-all. --deny-all restates the default and can make a deployment command's intent obvious. esdev is the exception: it grants everything for development.

NameCovers
readruntime:fs / runtime:wasi reads
writeruntime:fs / runtime:wasi mutations
importsStatic imports, package imports and dynamic import()
netFetch, WebSocket, outbound sockets and UDP send
listenListening sockets and HTTP/WebSocket servers
envruntime:process environment and cwd
runruntime:system child processes
signalsruntime:process signal handlers
workersnew Worker(...)

A run with no grants still executes its named entry file. Since imports is denied too, that is a single-file run; add --allow-imports for dependencies.

esdev --trace-permissions app.js runs the program with development grants and prints the esrun line it actually needs.

Seven of the nine can be narrowed to a list. imports and workers are all-or-nothing at the command line:

Shell
esrun --allow-imports --allow-env=PORT,DATABASE_URL \
      --allow-net=db.internal:5432 --allow-listen=8080 \
      --allow-read=./data --allow-write=./out --allow-run=git \
      --allow-signals=SIGTERM server.js

Hosts and paths are matched exactly. example.com does not admit api.example.com, and there are no wildcards. Paths are canonicalized before checking, so a symlink cannot escape the jail. A scoped refusal is ERR_PERMISSION_DENIED; a missing capability is ERR_CAPABILITY_DENIED.

Malformed or misplaced flags are errors, not warnings:

WrittenWhy it fails
--deny-run=gitDenials are all-or-nothing
--allow-env=A,,BThe scope contains an empty entry
--allow-net example.comA value cannot be a separate argument
esrun app.js --deny-netFlags after the script restrict nothing
--deny-net without --allow-allThere is no granted capability to remove
--allow-ffiIt is not a supported capability
--allow-workers=xWorker grants are set at spawn time
JavaScript
import { permissions } from "runtime:process";

permissions.denied;       // ["read", "write"]
permissions.has("net");  // false

For the separate question of which modules may load, see Import policy.

Last updated on
Edit this page