Skip to main content
Operating System

Permissions

Define scoped agentOS kernel permissions that authorize guest filesystem, process, and network operations for each VM and session.

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 EACCES before 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/env are allowed because they are fully virtualized (the guest sees only the VM, never the host) and are required to run a program at all.
  • network is 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

ScopeControlsDefault
fsFilesystem reads, writes, and metadata operationsallow
networkOutbound connections: fetch, HTTP, DNS, and inbound listendeny
childProcessSpawning child processesallow
processProcess-control operationsallow
envEnvironment variable accessallow
bindingInvoking bindings registered with the runtimedeny*

* 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";

// 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