Policy, commands & sandboxing
Autonomy levels, the command allowlist and risk classification, path sandboxing, rate limits, and OS sandbox backends.
Every agent tool call passes through Revka’s SecurityPolicy before it is
allowed to run. The policy enforces an autonomy level, a command
allowlist with a quote-aware shell parser, risk classification, a
workspace boundary, and a sliding-window rate limiter — and OS-level
sandbox backends wrap the command afterward. This page is the configuration
reference for all of those gates: what each one checks, the order they fire in,
and the exact [autonomy] and [security.sandbox] keys that tune them.
Use this page when you are hardening a deployment, debugging why a command was blocked, or deciding which commands and paths an agent may touch. For the mental model of how these layers compose with encryption, auditing, and containment, read the Security model first. For autonomy and approvals at the conceptual level, see Autonomy levels & approvals.
The [autonomy] config section
Section titled “The [autonomy] config section”[autonomy] is the single section that controls what the agent may do: its
autonomy level, the shell allowlist, filesystem boundaries, approval gates, and
per-policy budgets.
[autonomy]level = "supervised"workspace_only = trueallowed_commands = ["git", "python", "node"]forbidden_paths = ["/etc", "/root", "/proc", "/sys", "~/.ssh", "~/.gnupg", "~/.aws"]allowed_roots = ["~/Desktop/projects", "/opt/shared-repo"]max_actions_per_hour = 20max_cost_per_day_cents = 500require_approval_for_medium_risk = trueblock_high_risk_commands = true| Key | Type | Default | Meaning |
|---|---|---|---|
level | string | "supervised" | Autonomy mode: read_only, supervised, or full. |
workspace_only | bool | true | Reject absolute paths outside the workspace unless allowed_roots covers them. |
allowed_commands | list | [] | Shell command allowlist: names, full paths, or "*" to allow any (risk gates still apply). |
forbidden_paths | list | built-in list | System paths and sensitive dot-directories denied by default. |
allowed_roots | list | ["~/.revka/workflows", "~/.revka/artifacts", "~/.revka/workspace"] | Additional roots allowed outside the workspace. |
max_actions_per_hour | u32 | 20 | Per-policy action budget (sliding 1-hour window). |
max_cost_per_day_cents | u32 | 500 | Per-policy daily LLM-spend guardrail (US$5.00). |
require_approval_for_medium_risk | bool | true | Approval gate for medium-risk commands. |
block_high_risk_commands | bool | true | Hard block for high-risk commands. |
auto_approve | list | built-in read-only tool list (file_read, web_search_tool, web_fetch, calculator, glob_search, content_search, image_info, weather; merged with user entries) | Tool operations always auto-approved. |
always_ask | list | [] | Tool operations that always require approval. |
The current autonomy level is injected into the LLM system prompt automatically so the model knows its own constraints.
Autonomy levels
Section titled “Autonomy levels”The policy engine’s entry point is a three-tier mode that decides whether the agent can act at all.
| Level | Behaviour |
|---|---|
read_only | Observe only. All tool actions are blocked — even safe reads like ls and cat. The safest choice for untrusted channels. |
supervised | Default. Acts within the allowlists; medium- and high-risk commands require an explicit approval (approved = true). |
full | No approval gates, but still enforces the command allowlist, risk blocking, workspace boundary, and rate limits. |
[autonomy]level = "supervised" # read_only | supervised | fullCommand allowlist & shell parser
Section titled “Command allowlist & shell parser”Every shell command is checked against allowed_commands before execution. The
check uses a quote-aware shell lexer that splits compound input on unquoted
;, |, &&, ||, and newlines, then validates each sub-command
individually. This prevents bypass through command chaining — you cannot smuggle
a blocked command past the gate by appending it after an allowed one.
[autonomy]allowed_commands = ["git", "npm", "cargo", "ls", "cat", "grep", "find", "echo", "pwd", "wc", "head", "tail", "date", "df", "du", "uname", "uptime", "hostname", "python", "python3", "pip", "node"]The five bypass-prevention gates
Section titled “The five bypass-prevention gates”Each candidate runs through five independent checks, in this order. Failing any one rejects the command:
-
Subshell operators — backticks
`,$(...), and process substitution<(...)/>(...)are rejected. -
Redirections — input/output redirects
<and>are rejected. -
tee— always blocked regardless of the allowlist, because it writes to arbitrary files. -
Background
&— a single trailing&(backgrounding) is rejected. -
Per-segment allowlist — each split sub-command must match an entry by basename, full path, or the wildcard
"*".
Allowlist entry forms
Section titled “Allowlist entry forms”| Form | Matches | Example |
|---|---|---|
| Bare name | Any executable with that basename | "git" matches /usr/bin/git and git |
| Full path | Only that exact executable path | "/usr/bin/antigravity" |
Wildcard "*" | Any command name | "*" |
Even with "*", dangerous argument patterns stay blocked: find -exec,
find -ok, git config, git -c, and git alias are rejected because they
allow arbitrary argument injection even when git or find is on the
allowlist. The "*" wildcard also does not bypass
block_high_risk_commands — the high-risk safety net remains.
Command risk classification
Section titled “Command risk classification”Every command — and each segment of a compound command — is classified Low / Medium / High risk. A pipeline or chain inherits the highest risk level of any segment. The classifier evaluates each candidate after argv parsing by:
- Looking up its basename (
rm,git,powershell, …). - For commands whose risk depends on a subcommand verb (
git,npm,cargo), checking that verb against a whitelist. - Scanning the joined command string for known destructive patterns
even when the basename alone looks benign (
rm -rf /, fork-bomb,format c:).
[autonomy]block_high_risk_commands = true # default — High commands rejected before dispatchrequire_approval_for_medium_risk = true # default — Medium commands route to the approval queueHigh risk
Section titled “High risk”Blocked entirely when block_high_risk_commands = true (the shipped default).
When set to false, High commands run through the same approval gate as
medium-risk instead.
| Category | Commands |
|---|---|
| File destruction | rm, mkfs, dd |
| Power / boot | shutdown, reboot, halt, poweroff |
| Privilege escalation | sudo, su, runas |
| Permissions / ownership | chown, chmod, icacls, takeown |
| Account management | useradd, userdel, usermod, passwd |
| Filesystem mounting | mount, umount |
| Firewall | iptables, ufw, firewall-cmd, netsh |
| Outbound network | curl, wget, nc, ncat, netcat, scp, ssh, ftp, telnet |
| Windows registry / service | reg, net, wmic, sc |
| Windows shells / scripts | powershell, pwsh |
| Windows destructive | del, rmdir, format |
Destructive-pattern matchers (also classified High even if the basename is benign):
rm -rf /andrm -fr /anywhere in the joined segment- The classic fork-bomb literal
:(){:|:&};: - Windows recursive-delete literals
del /s /qandrmdir /s /q - The
format c:literal
Medium risk
Section titled “Medium risk”Requires approval when require_approval_for_medium_risk = true (the shipped
default). When level = "full", this gate is skipped (other guardrails still
apply).
| Tool | Verbs that flip to Medium |
|---|---|
git | commit, push, reset, clean, rebase, merge, cherry-pick, revert, branch, checkout, switch, tag |
npm / pnpm / yarn | install, add, remove, uninstall, update, publish |
cargo | add, remove, install, clean, publish |
Bare-basename Medium (any args): touch, mkdir, mv, cp, ln, plus
Windows equivalents copy, xcopy, robocopy, move, ren, rename,
mklink.
Low risk
Section titled “Low risk”Everything else — read-only or trivially safe commands run without approval
gating: ls, cat, grep, find, git status, git log, npm ls,
cargo check, cargo test, which, ps, df. The classifier deliberately
defaults to Low rather than enumerating every safe command, so the High and
Medium lists stay conservative.
Path sandboxing & workspace boundary
Section titled “Path sandboxing & workspace boundary”Six independent checks keep file operations inside the workspace, plus a post-canonicalization check that defeats symlink escapes.
[autonomy]workspace_only = true # default — restrict to the workspaceallowed_roots = ["/data/shared", "~/projects/other"]forbidden_paths = ["/etc", "/root", "/home", "~/.ssh", "~/.aws", "~/.config"]The checks, in order:
- Null-byte injection — paths containing
\0are rejected. ..traversal — any..path component is rejected.- URL-encoded traversal — encoded forms such as
..%2fare rejected. ~usertilde expansion —~otheruser/...references are rejected.- Workspace-only absolute-path lock — when
workspace_only = true, absolute paths must fall underworkspace_diror an entry inallowed_roots. - Forbidden-prefix blocklist — the path is prefix-matched against
forbidden_paths(e.g./etc,/root,~/.ssh,~/.aws).
After the path is joined and canonicalized, an additional resolved-path check re-validates the final location, which is what catches symlink escapes (a link inside the workspace that points outside it).
Field reference
Section titled “Field reference”| Key | Type | Default | Meaning |
|---|---|---|---|
workspace_only | bool | true | When false, paths outside the workspace are blocked only by forbidden_paths. |
allowed_roots | list | ["~/.revka/workflows", "~/.revka/artifacts", "~/.revka/workspace"] | Absolute paths the agent may access even when workspace_only = true. Tilde expansion applies; relative entries resolve against the workspace. |
forbidden_paths | list | built-in | Prefix-matched against the tilde-expanded path; checked after the workspace / allowed-root pass. |
Revka’s own runtime config files (config.toml, active_workspace.toml) are
separately guarded against agent modification. That protection is silent and
non-overridable.
Rate limiting (actions & cost)
Section titled “Rate limiting (actions & cost)”Two independent budgets bound how much the agent can do. A sliding one-hour
window tracks action counts; when the agent reaches max_actions_per_hour,
all Act-type tool operations are blocked until the window expires. A separate
daily cap (max_cost_per_day_cents) bounds LLM API spend. Read operations
(file reads, etc.) are never rate-limited.
[autonomy]max_actions_per_hour = 20 # defaultmax_cost_per_day_cents = 500 # default (US$5.00)| Key | Type | Default | Meaning |
|---|---|---|---|
max_actions_per_hour | u32 | 20 | Counted by an in-memory sliding window. |
max_cost_per_day_cents | u32 | 500 | Enforced by a separate cost tracker. |
When the action budget is exhausted the policy returns
Rate limit exceeded: action budget exhausted.
OS-level sandbox backends
Section titled “OS-level sandbox backends”On top of the application-layer policy, command execution can be wrapped in an
OS isolation backend. Auto-detection picks the best available backend at
startup. Each backend implements a single Sandbox trait, so they are pluggable
and platform-agnostic.
[security.sandbox]enabled = truebackend = "auto" # auto | none | firejail | bubblewrap | landlock | sandbox-exec | docker| Backend | Platform | Notes |
|---|---|---|
firejail | Linux | User-space sandboxing, no root required. Easiest to set up on Linux. |
bubblewrap | Linux / macOS | User-namespace containers. Requires the sandbox-bubblewrap feature. |
landlock | Linux 5.13+ | Kernel-enforced filesystem restrictions; the strongest isolation. Requires the sandbox-landlock feature. |
sandbox-exec | macOS | Apple Seatbelt sandbox profiles. |
docker | All | Subprocess isolation inside a Docker container; adds spawn overhead. |
none | All | Application-layer security only; the always-available fallback. |
| Key | Type | Default | Meaning |
|---|---|---|---|
enabled | bool | — | Set false to disable all OS sandboxing. |
backend | string | "auto" | "auto" selects the best available backend; or name one explicitly. |
If a requested backend is unavailable at runtime, Revka falls back to a no-op sandbox and logs a warning — it never silently fails open in the policy engine, which still enforces the allowlist, risk gates, and path boundary.
Building with sandbox features
Section titled “Building with sandbox features”The heavyweight backends are behind compile-time Cargo feature flags, so a disabled backend adds zero binary bloat.
cargo build --release --features sandbox-landlockcargo build --release --features sandbox-bubblewrap# Firejail, sandbox-exec, Docker, and the no-op fallback need no extra features.cargo build --releaseSee Cargo feature flags & ADRs for the full feature matrix.
Sandboxing roadmap
Section titled “Sandboxing roadmap”The backends above are wired into the runtime today, behind feature flags and auto-detection. The original sandboxing proposal additionally sketched per-backend tuning that is not yet a stable config surface:
- Firejail extra arguments (e.g.
--seccomp,--caps.drop=all). - Landlock explicit read-only / read-write path lists.
Treat those tuning knobs as roadmap rather than current behavior. The
[security.sandbox] keys documented above — enabled and backend — are the
supported surface today.
Hardening checklist
Section titled “Hardening checklist”-
Pick the right autonomy level. Use
read_onlyfor untrusted channels; keepsupervised(the default) where a human can approve risky actions; usefullonly for trusted, scoped automation. -
Scope the allowlist tightly. Prefer an explicit
allowed_commandslist over"*". Avoid combining"*"withblock_high_risk_commands = false. -
Lock the workspace. Keep
workspace_only = trueand add only the specificallowed_rootsthe agent needs. -
Set realistic budgets. Tune
max_actions_per_hourandmax_cost_per_day_centsto your workload, and pair them with the durable[cost]budgets. -
Turn on an OS sandbox. Set
[security.sandbox].enabled = trueand letbackend = "auto"choose, or name a backend you have verified is available. -
Verify after every change. Run
revka doctorand review the audit log to confirm gates behave as intended.