Permissions
The sandbox permission policy is the kernel-level enforcement layer. Every guest syscall the agent’s sandboxed code makes is checked against a per-scope policy before any host resource is touched.
- Six scopes, configured independently:
fs,network,childProcess,process,env,binding. - Each scope is a mode (
"allow"or"deny"), or a rule set. - A denied operation is rejected with
EACCESbefore any host resource is touched. - Merged over a secure default, so partial policies work.
For the higher-level agent tool-approval layer (human-in-the-loop, auto-approve), see Approvals.
Defaults and merge semantics
Section titled “Defaults and merge semantics”The sandbox is deny-by-default for outward-facing capabilities. When you pass no policy, this baseline applies:
{ fs: "allow", // virtualized in-memory filesystem only childProcess: "allow", process: "allow", env: "allow", network: "deny", // no network egress until you opt in}fs/childProcess/process/envare allowed because they are fully virtualized (the guest sees only the VM, never the host) and are required to run a program at all.networkis denied: guest code cannot reach the network until you opt in.- Your policy is merged over this baseline. Omitted scopes keep their default; they are not denied. So
{ network: "allow" }grants the network while keeping the execution essentials.
// Grant the network, leave everything else at the secure default.const grantNetwork = { network: "allow" };Permission scopes
Section titled “Permission scopes”| Scope | Controls | Default |
|---|---|---|
fs | Filesystem reads, writes, and metadata operations | allow |
network | Outbound connections: fetch, HTTP, DNS, and inbound listen | deny |
childProcess | Spawning child processes | allow |
process | Process-control operations | allow |
env | Environment variable access | allow |
binding | Invoking bindings registered with the runtime | deny* |
* The binding scope is auto-granted to allow when you register bindings and set no binding policy of your own. Pass a binding policy to gate individual bindings.
Bind a policy to the VM
Section titled “Bind a policy to the VM”A policy is a plain object keyed by scope. Pass it as permissions to agentOS(...) and it gates every guest syscall on that VM.
import { agentOS, setup } from "@rivet-dev/agentos";
const vm = agentOS({ permissions: { network: "allow", fs: "deny", },});
export const registry = setup({ use: { vm } });registry.start();Grant or deny a whole scope
Section titled “Grant or deny a whole scope”The simplest value for a scope is a single mode string. "allow" permits every operation in the scope; "deny" rejects every one with EACCES. Omitted scopes keep their secure default, so you only list what you want to change.
const permissions = { network: "allow", // turn on network egress fs: "deny", // turn off all filesystem access};There is no typed "ask" mode. Interactive, human-in-the-loop approval lives in the higher-level Approvals layer, not the kernel policy. To block at the kernel level, use "deny".
Allow only specific filesystem paths
Section titled “Allow only specific filesystem paths”For finer control, a scope can be a rule set instead of a bare mode: a default mode plus an ordered list of rules. The fs scope matches by paths (filesystem globs). Each rule names its operations (read, write, stat, readdir, create_dir, rm, rename, symlink, readlink, chmod, truncate, mount_sensitive, or ["*"] for all). Last matching rule wins; if no rule matches, default applies.
// Allow the filesystem everywhere, but deny anything under /home/agentos/vault.const denyVault = { fs: { default: "allow", rules: [{ mode: "deny", operations: ["*"], paths: ["/home/agentos/vault/**"] }], },};To invert it, flip default to "deny" and allow just one subtree:
// Deny the filesystem by default, allow only reads under /home/agentos/data.const allowOnlyData = { fs: { default: "deny", rules: [{ mode: "allow", operations: ["read", "readdir", "stat"], paths: ["/home/agentos/data/**"] }], },};Allow only specific network hosts
Section titled “Allow only specific network hosts”Every non-fs scope matches by patterns instead of paths. For network, a pattern is a host (or host:port), and the operations are fetch, http, dns, and listen.
// Deny the network by default, allow only api.example.com.const allowOneHost = { network: { default: "deny", rules: [{ mode: "allow", operations: ["*"], patterns: ["api.example.com"] }], },};Allow only specific bindings
Section titled “Allow only specific bindings”Bindings registered with the runtime are gated by the binding scope, matched by name via patterns. Bindings have no sub-operations, so pass ["*"] for operations.
// Deny all bindings by default, allow only the "add" binding by name.const allowOneBinding = { binding: { default: "deny", rules: [{ mode: "allow", operations: ["*"], patterns: ["add"] }], },};The childProcess, process, and env scopes work the same way: childProcess patterns match the command (operations: ["spawn"]), env patterns match the variable name (operations: ["read", "write"]), and process is matched by pattern with operations: ["*"].
Combine policies and see denials
Section titled “Combine policies and see denials”Each policy above sets one scope, so you can spread several into one permissions object and bind them together.
import { agentOS, setup } from "@rivet-dev/agentos";
const vm = agentOS({ permissions: { ...denyVault, ...allowOneHost, ...allowOneBinding, },});
export const registry = setup({ use: { vm } });registry.start();When a scope or matching rule denies an operation, the kernel rejects it with EACCES before any host resource is touched. For example, with network: "deny", an outbound fetch() inside the guest throws:
EACCES: permission denied, tcp://example.com:80: blocked by network.http policy