Permissions
The per-scope kernel permission policy that gates every guest syscall in the sandbox.
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
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" } satisfies Permissions;
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
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
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
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/**"] }],
},
} satisfies Permissions;
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/**"] }],
},
} satisfies Permissions;
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"] }],
},
} satisfies Permissions;
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"] }],
},
} satisfies Permissions;
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
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";
import type { Permissions } from "@rivet-dev/agentos-core";
// Allow the filesystem everywhere, but deny anything under /home/agentos/vault.
const denyVault = {
fs: {
default: "allow",
rules: [{ mode: "deny", operations: ["*"], paths: ["/home/agentos/vault/**"] }],
},
} satisfies Permissions;
// Deny the network by default, allow only api.example.com.
const allowOneHost = {
network: {
default: "deny",
rules: [{ mode: "allow", operations: ["*"], patterns: ["api.example.com"] }],
},
} satisfies Permissions;
// Deny all bindings by default, allow only the "add" binding by name.
const allowOneBinding = {
binding: {
default: "deny",
rules: [{ mode: "allow", operations: ["*"], patterns: ["add"] }],
},
} satisfies Permissions;
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