- addons
Modern Addon Permissions and Sandbox
Addon code runs in a locked-down sandbox where everything is denied by default. You opt in to each capability with explicit allowlists on the definition, which the build mirrors into the manifest.
Declaring Permissions
permissions: {
fs: ["/data/exports/**"], // filesystem globs
net: ["api.example.com"], // outbound hostnames
env: ["MY_API_TOKEN"], // readable process.env keys
spawn: true, // ctx.spawn, local agent only
ai: true, // ctx.ai
ocr: true, // ctx.ocr
}What Each Permission Unlocks
| Permission | Default | Unlocks | On violation |
|---|---|---|---|
| fs | tmpDir only | Reading and writing the listed globs | Filesystem call throws |
| net | No network | fetch to the listed hostnames | Request rejected |
| env | No access | Reading the listed process.env keys | Key reads as undefined |
| spawn | false | ctx.spawn.run() | On cloud, every call rejects |
| ai | false | ctx.ai | Field absent |
| ocr | false | ctx.ocr | Field absent |
fs, net, and env apply to actions, test data functions, and hooks alike. spawn, ai, and ocr are honored for action addons only, so hook and test data function contexts never expose them.
ctx.tmpDir is always readable and writable without an fs entry.
Subprocess Execution Is Local Only
ctx.spawn works only on a local or hybrid agent. On cloud executions the broker is not attached and every ctx.spawn.run() rejects. Design your addon to surface that failure clearly rather than hanging.
Subprocess execution is host-brokered. The command runs in the agent process using execFile, taking a command and an argument vector with no shell, so raw child_process imports stay blocked.
const { stdout, exitCode } = await ctx.spawn.run("node", ["-e", "console.log('hi')"]);The result is { stdout, stderr, exitCode, signal, truncated }. The child inherits the agent's environment, including PATH, HOME, and credentials, so real CLIs resolve. Output is buffered and capped, and each run has a timeout.
What Is Always Blocked
Dynamic code evaluation is blocked, including eval and the raw Function constructor.
Raw child_process, worker_threads, and the raw node:http, node:https, and node:net modules are always blocked. No permission unlocks a direct import, and a top-level import of one prevents the bundle from loading at all.
Make outbound calls with the global fetch, gated by permissions.net. Run local commands with ctx.spawn, gated by permissions.spawn.
Request Only What You Need
Reviewers and test authors see your declared permissions. A tight allowlist makes your addon easier to trust and approve, which matters when a security team reviews third-party code.
