Skip to content

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.

[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 = true
allowed_commands = ["git", "python", "node"]
forbidden_paths = ["/etc", "/root", "/proc", "/sys", "~/.ssh", "~/.gnupg", "~/.aws"]
allowed_roots = ["~/Desktop/projects", "/opt/shared-repo"]
max_actions_per_hour = 20
max_cost_per_day_cents = 500
require_approval_for_medium_risk = true
block_high_risk_commands = true
KeyTypeDefaultMeaning
levelstring"supervised"Autonomy mode: read_only, supervised, or full.
workspace_onlybooltrueReject absolute paths outside the workspace unless allowed_roots covers them.
allowed_commandslist[]Shell command allowlist: names, full paths, or "*" to allow any (risk gates still apply).
forbidden_pathslistbuilt-in listSystem paths and sensitive dot-directories denied by default.
allowed_rootslist["~/.revka/workflows", "~/.revka/artifacts", "~/.revka/workspace"]Additional roots allowed outside the workspace.
max_actions_per_houru3220Per-policy action budget (sliding 1-hour window).
max_cost_per_day_centsu32500Per-policy daily LLM-spend guardrail (US$5.00).
require_approval_for_medium_riskbooltrueApproval gate for medium-risk commands.
block_high_risk_commandsbooltrueHard block for high-risk commands.
auto_approvelistbuilt-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_asklist[]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.

The policy engine’s entry point is a three-tier mode that decides whether the agent can act at all.

LevelBehaviour
read_onlyObserve only. All tool actions are blocked — even safe reads like ls and cat. The safest choice for untrusted channels.
supervisedDefault. Acts within the allowlists; medium- and high-risk commands require an explicit approval (approved = true).
fullNo approval gates, but still enforces the command allowlist, risk blocking, workspace boundary, and rate limits.
[autonomy]
level = "supervised" # read_only | supervised | full

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"]

Each candidate runs through five independent checks, in this order. Failing any one rejects the command:

  1. Subshell operators — backticks `, $(...), and process substitution <(...) / >(...) are rejected.

  2. Redirections — input/output redirects < and > are rejected.

  3. tee — always blocked regardless of the allowlist, because it writes to arbitrary files.

  4. Background & — a single trailing & (backgrounding) is rejected.

  5. Per-segment allowlist — each split sub-command must match an entry by basename, full path, or the wildcard "*".

FormMatchesExample
Bare nameAny executable with that basename"git" matches /usr/bin/git and git
Full pathOnly 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.

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:

  1. Looking up its basename (rm, git, powershell, …).
  2. For commands whose risk depends on a subcommand verb (git, npm, cargo), checking that verb against a whitelist.
  3. 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 dispatch
require_approval_for_medium_risk = true # default — Medium commands route to the approval queue

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.

CategoryCommands
File destructionrm, mkfs, dd
Power / bootshutdown, reboot, halt, poweroff
Privilege escalationsudo, su, runas
Permissions / ownershipchown, chmod, icacls, takeown
Account managementuseradd, userdel, usermod, passwd
Filesystem mountingmount, umount
Firewalliptables, ufw, firewall-cmd, netsh
Outbound networkcurl, wget, nc, ncat, netcat, scp, ssh, ftp, telnet
Windows registry / servicereg, net, wmic, sc
Windows shells / scriptspowershell, pwsh
Windows destructivedel, rmdir, format

Destructive-pattern matchers (also classified High even if the basename is benign):

  • rm -rf / and rm -fr / anywhere in the joined segment
  • The classic fork-bomb literal :(){:|:&};:
  • Windows recursive-delete literals del /s /q and rmdir /s /q
  • The format c: literal

Requires approval when require_approval_for_medium_risk = true (the shipped default). When level = "full", this gate is skipped (other guardrails still apply).

ToolVerbs that flip to Medium
gitcommit, push, reset, clean, rebase, merge, cherry-pick, revert, branch, checkout, switch, tag
npm / pnpm / yarninstall, add, remove, uninstall, update, publish
cargoadd, remove, install, clean, publish

Bare-basename Medium (any args): touch, mkdir, mv, cp, ln, plus Windows equivalents copy, xcopy, robocopy, move, ren, rename, mklink.

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.

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 workspace
allowed_roots = ["/data/shared", "~/projects/other"]
forbidden_paths = ["/etc", "/root", "/home", "~/.ssh", "~/.aws", "~/.config"]

The checks, in order:

  1. Null-byte injection — paths containing \0 are rejected.
  2. .. traversal — any .. path component is rejected.
  3. URL-encoded traversal — encoded forms such as ..%2f are rejected.
  4. ~user tilde expansion~otheruser/... references are rejected.
  5. Workspace-only absolute-path lock — when workspace_only = true, absolute paths must fall under workspace_dir or an entry in allowed_roots.
  6. 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).

KeyTypeDefaultMeaning
workspace_onlybooltrueWhen false, paths outside the workspace are blocked only by forbidden_paths.
allowed_rootslist["~/.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_pathslistbuilt-inPrefix-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.

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 # default
max_cost_per_day_cents = 500 # default (US$5.00)
KeyTypeDefaultMeaning
max_actions_per_houru3220Counted by an in-memory sliding window.
max_cost_per_day_centsu32500Enforced by a separate cost tracker.

When the action budget is exhausted the policy returns Rate limit exceeded: action budget exhausted.

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 = true
backend = "auto" # auto | none | firejail | bubblewrap | landlock | sandbox-exec | docker
BackendPlatformNotes
firejailLinuxUser-space sandboxing, no root required. Easiest to set up on Linux.
bubblewrapLinux / macOSUser-namespace containers. Requires the sandbox-bubblewrap feature.
landlockLinux 5.13+Kernel-enforced filesystem restrictions; the strongest isolation. Requires the sandbox-landlock feature.
sandbox-execmacOSApple Seatbelt sandbox profiles.
dockerAllSubprocess isolation inside a Docker container; adds spawn overhead.
noneAllApplication-layer security only; the always-available fallback.
KeyTypeDefaultMeaning
enabledboolSet false to disable all OS sandboxing.
backendstring"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.

The heavyweight backends are behind compile-time Cargo feature flags, so a disabled backend adds zero binary bloat.

Terminal window
cargo build --release --features sandbox-landlock

See Cargo feature flags & ADRs for the full feature matrix.

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.

  1. Pick the right autonomy level. Use read_only for untrusted channels; keep supervised (the default) where a human can approve risky actions; use full only for trusted, scoped automation.

  2. Scope the allowlist tightly. Prefer an explicit allowed_commands list over "*". Avoid combining "*" with block_high_risk_commands = false.

  3. Lock the workspace. Keep workspace_only = true and add only the specific allowed_roots the agent needs.

  4. Set realistic budgets. Tune max_actions_per_hour and max_cost_per_day_cents to your workload, and pair them with the durable [cost] budgets.

  5. Turn on an OS sandbox. Set [security.sandbox].enabled = true and let backend = "auto" choose, or name a backend you have verified is available.

  6. Verify after every change. Run revka doctor and review the audit log to confirm gates behave as intended.