What a script is allowed to do, decided in one place.
QuickJS itself has no ambient authority: a fresh realm can compute
and nothing else. Everything a script can reach beyond that — a file,
a socket, an environment variable, a fact about the host — is a
capability the embedding runtime installed, and every one of those
installs asks this crate before acting. Deny is the default for each
kind; a host grants the least it can.
The model is Deno's and Node's, which are the two that survived:
- [
Permissions] is the policy: five kinds ([Kind]), each a [Allow] of none, everything, or a list, plus a [Deny] list per kind that overrides the allow (read: all, deny read: /etc). - [
Container] holds the policy for ONE realm for the realm's whole life. It can only ever narrow ([Container::revoke], irreversible, like Node'sprocess.permission.dropand Deno'srevoke). It also carries a [Hook] the host may install to grant on demand (a prompt) and an [Audit] that sees every decision.
What the model deliberately does NOT have is a dynamic scope: no "narrow the policy around this call and carry it into the callbacks it registers". That is Java's stack-inspection Security Manager, removed by JEP 411 as brittle, slow, and impossible to keep complete across an API surface. A host with two trust levels runs them in two realms, each with its own container, the way workerd gives each isolate its own bindings.
Paths are checked twice: as written, after lexical normalisation, and as the filesystem will actually resolve them, after following every symlink in the longest existing prefix. Both must fall under a granted root, so a link planted inside an allowed directory cannot point out of it. Node documents the opposite (links are followed out) as a hazard the operator must avoid; this does not leave it to them.