Policy engine
SecurityPolicy enforces an autonomy level, a command allowlist with a
quote-aware shell parser, risk classification, a workspace boundary, and a
sliding-window rate limiter.
Revka's defence-in-depth security layers from policy engine to OS sandboxing, encryption, and audit.
Revka is audit-first by design. Before any agent tool runs, the action passes through independent, overlapping security layers — a policy engine, OS-level sandboxing, encrypted credential storage, and a tamper-evident audit log. This page explains those layers, how they compose, the philosophy behind them, and how to report a vulnerability.
Read this to build a mental model of where enforcement happens and which knobs to turn for your threat profile. For copy-paste configuration and command reference, follow the links into the dedicated Security section as you go.
Every defence is evaluated before a tool is allowed to act, and no single bypass defeats the model — a command must clear the autonomy gate, the allowlist, risk classification, path sandboxing, and rate limits, then run inside an OS sandbox, with the whole turn recorded to an append-only audit log.
Policy engine
SecurityPolicy enforces an autonomy level, a command allowlist with a
quote-aware shell parser, risk classification, a workspace boundary, and a
sliding-window rate limiter.
OS sandboxing
Optional kernel/user-space isolation backends (Firejail, Bubblewrap, Landlock, sandbox-exec, Docker) wrap command execution, auto-detected at startup.
Encryption & auth
Secrets are encrypted at rest with ChaCha20-Poly1305; pairing tokens are SHA-256 hashed; bearer tokens gate the gateway API.
Audit & containment
A tamper-evident Merkle audit log records every event; emergency stop, OTP gating, and trust scoring contain a misbehaving agent in real time.
The model protects against the agent’s three highest-risk failure modes:
unauthorized shell execution (rm -rf /, curl | sh), workspace escape (path
traversal, symlinks, absolute paths), and runaway cost from LLM API calls.
The layers below are ordered from the innermost policy check outward. Each is independent: a tool call must satisfy all of them.
The policy engine’s entry point is a three-tier autonomy mode that controls whether the agent can act at all. The current level is injected into the LLM system prompt automatically.
| Level | Behaviour |
|---|---|
read_only | Observe only — all tool actions are blocked, even safe reads like ls/cat. Safest choice for untrusted channels. |
supervised | Default. Acts within allowlists; medium- and high-risk commands require an explicit approval (approved = true). |
full | No approval gates, but still enforces the command allowlist and workspace boundary. |
[autonomy]level = "supervised" # read_only | supervised | fullEvery shell command is checked against an explicit allowlist before execution.
A quote-aware lexer splits compound input on unquoted ;, |, &&, ||, and
newlines, then validates each sub-command independently — so chaining cannot
smuggle a blocked command past the gate.
[autonomy]allowed_commands = ["git", "npm", "cargo", "ls", "cat", "grep", "find", "echo", "pwd", "wc", "head", "tail", "python", "node"]Five independent gates run, in order:
$(...), and process substitution
<(...) / >(...) are rejected.< and > are rejected.tee — always blocked regardless of the allowlist (it writes to
arbitrary files).& — a single trailing & is rejected."*".Allowlist entries may be bare names (matched by basename), full paths
(e.g. /usr/bin/antigravity, exact match only), or "*". Even with "*",
dangerous argument patterns stay blocked: find -exec, find -ok,
git config, git -c, and git alias.
Each command — and each segment of a compound command — is classified Low / Medium / High risk; the highest classification across all segments wins.
| Risk | Examples | Default handling |
|---|---|---|
| High | rm, dd, mkfs, sudo, su, chmod, chown, mount, iptables, curl, wget, nc, ssh, scp; Windows del, format, reg, net, runas, powershell | Blocked unless explicitly allowlisted |
| Medium | git state-changers (commit, push, reset, rebase, checkout); npm/cargo install/publish; mkdir, mv, cp, ln | Requires approval in supervised |
| Low | ls, cat, grep, pwd, echo, date | Allowed (subject to allowlist) |
[autonomy]block_high_risk_commands = true # defaultrequire_approval_for_medium_risk = true # defaultSix independent checks keep file operations inside the workspace, plus a post-canonicalization check that defeats symlink escapes:
.. component traversal rejection...%2f) rejection.~user tilde-expansion rejection.workspace_dir or allowed_roots./etc, /root, ~/.ssh, ~/.aws, …).[autonomy]workspace_only = true # defaultallowed_roots = ["/data/shared", "~/projects/other"]forbidden_paths = ["/etc", "/root", "~/.ssh", "~/.aws", "~/.config"]Workspace and allowed_roots are checked before forbidden_paths, so a
workspace nested inside an otherwise-forbidden tree (for example a workspace
under /home when /home is forbidden) is still allowed. Revka’s own runtime
config files (config.toml, active_workspace.toml) are separately guarded
against agent modification — silently and non-overridably.
A sliding one-hour window caps how many Act-type operations the agent can run; a separate daily cap bounds LLM spend. Read operations are never rate-limited.
| Key | Type | Default | Meaning |
|---|---|---|---|
max_actions_per_hour | u32 | 20 | In-memory sliding window; resets on daemon restart |
max_cost_per_day_cents | u32 | 500 | Daily LLM spend cap (US$5.00) |
[autonomy]max_actions_per_hour = 20max_cost_per_day_cents = 500On 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; if a requested backend is unavailable, Revka falls back to a no-op sandbox with a warning.
| Backend | Platform | Notes |
|---|---|---|
firejail | Linux | User-space, no root. Easiest to set up. |
bubblewrap | Linux/macOS | User-namespace containers (feature sandbox-bubblewrap). |
landlock | Linux 5.13+ | Kernel-enforced filesystem restrictions; strongest isolation (feature sandbox-landlock). |
sandbox-exec | macOS | Apple Seatbelt profiles. |
docker | All | Container isolation; adds spawn overhead. |
none | All | Application-layer only; always-available fallback. |
[security.sandbox]enabled = truebackend = "auto" # auto | none | firejail | bubblewrap | landlock | sandbox-exec | dockerEvery security-relevant event is appended to a JSONL audit log. Each entry
carries a SHA-256 entry_hash computed over the previous hash plus the
canonical JSON of the entry, a monotonic sequence, and a prev_hash
back-link — forming a Merkle chain. Editing any entry invalidates every
subsequent hash. Entries can optionally be signed with HMAC-SHA256.
[audit]enabled = true # defaultlog_path = "audit.log" # relative to the revka dirmax_size_mb = 100sign_events = false # optional HMAC signingHMAC signing requires a 32-byte key, set before the gateway starts:
export REVKA_AUDIT_SIGNING_KEY="<64 hex chars = 32 bytes>"Query and verify the chain over the gateway API:
# Query recent eventscurl "http://127.0.0.1:42617/api/audit?limit=50&event_type=command_execution" \ -H "Authorization: Bearer rk_<token>"
# Verify chain integrity (detects tampering)curl "http://127.0.0.1:42617/api/audit/verify" \ -H "Authorization: Bearer rk_<token>"GET /api/audit accepts limit (default 50, max 500), event_type
(command_execution, file_access, config_change, auth_success,
auth_failure, policy_violation, security_event), and since (RFC 3339).
On rotation the chain resets to genesis, so each rotated archive verifies
independently.
Two cryptographic layers protect credentials and gateway access:
~/.revka/.secret_key (mode 0600 on Unix; restricted ACL on
Windows). Encrypted values appear as enc2:<hex>. Back up the key file —
losing it makes every enc2: value unrecoverable.require_pairing = true (default),
first startup prints a 6-digit one-time code. A client exchanges it via
POST /pair for a 256-bit bearer token prefixed rk_. Tokens are stored only
as SHA-256 hashes; after 5 failed attempts a client is locked out for 5
minutes.[secrets]encrypt = true # default
[gateway]require_pairing = true # defaultallow_public_bind = false # must be explicit to bind beyond localhost# Server prints "Pairing code: 123456" on first startcurl -X POST http://127.0.0.1:42617/pair \ -H 'Content-Type: application/json' \ -d '{"code": "123456", "device_name": "My Laptop", "device_type": "cli"}'# Returns: {"token": "rk_<64 hex chars>"}Three subsystems let you contain a misbehaving agent without restarting it:
revka estop). Persisted kill switch with four levels —
kill-all, network-kill, domain-block (glob patterns), and tool-freeze.
State survives restarts and is fail-closed: corrupt or unreadable state
defaults to kill_all = true. Resume can require a valid OTP.otpauth:// URI is shown once at first use.full → supervised → read_only).revka estop # engage kill-allrevka estop --level domain-block --domain "*.chase.com"revka estop --level tool-freeze --tool shellrevka estop resume --otp 123456 # OTP-gated resume[security.otp]enabled = truegated_actions = ["shell", "file_write", "browser_open"]gated_domains = ["*.chase.com"]
[security.estop]enabled = truerequire_otp_to_resume = true
[trust]initial_score = 0.8regression_threshold = 0.5Two heuristic scanners protect the boundary between the agent and the outside world:
Safe, Suspicious, or Blocked. Configurable action and
sensitivity.[REDACTED_*] placeholders.[security.prompt_guard]action = "warn" # warn | block | sanitizesensitivity = 0.7 # 0.0 lenient — 1.0 strict
[security.leak_detector]sensitivity = 0.7Two design principles shape how these layers are delivered.
Security never costs you portability. The heavyweight protections are
optional Cargo feature flags behind platform-specific conditional
compilation, so when a feature is disabled its code is not compiled in — zero
binary bloat. Sandbox backends are pluggable behind a single Sandbox trait
(just like providers, channels, and memory), and the same binary runs across
Linux (x86_64, ARM, RISC-V), macOS, and Windows, adapting its protection level
to what the host supports.
# Standard release — application-layer security always oncargo build --release
# Add a platform sandbox at compile timecargo build --release --features sandbox-landlock # Linuxcargo build --release --features sandbox-bubblewrap # macOS/LinuxSecurity should be like an airbag — present, protective, and invisible until
needed. The onboarding wizard adds no extra steps and writes no security
sections to config.toml unless you customize them; sensible defaults
(supervised autonomy, workspace-scoped access, secrets encryption,
auto-detected sandbox) apply silently. Advanced users keep explicit control
through the [autonomy], [security.*], and [secrets] config sections.
The layers documented above are enforced by the runtime today. A few related capabilities are gated or staged:
| Capability | Status |
|---|---|
| Policy engine, command allowlist, risk classification, path sandboxing, rate limiting | Current |
| Audit log, encrypted secrets, device pairing, OTP, emergency stop, prompt-injection & leak detection, trust scoring | Current |
| OS sandbox backends (Firejail, Bubblewrap, Landlock, sandbox-exec, Docker) | Current, behind feature flags / auto-detection |
| WebAuthn / FIDO2 gateway login | Feature-gated — build with --features webauthn |
| Nevis IAM (OAuth2/OIDC, FIDO2, role mappings) | Enterprise — see Enterprise IAM (Nevis) |
| Verifiable Intent (SD-JWT commerce gating) | Advanced — see Verifiable Intent |
Revka’s Docker images follow CIS Docker Benchmark practices: the runtime stage
is a distroless nonroot image with no shell and no package manager, the
container runs as a non-root UID, and a read-only root filesystem is supported.
| Control | Implementation |
|---|---|
| Non-root user | Runs as UID 65534 (distroless nonroot) |
| Minimal base image | gcr.io/distroless/cc-debian12:nonroot |
| Read-only filesystem | docker run --read-only with a /workspace volume |
# Verify the image runs as non-rootdocker build -t revka .docker inspect --format='{{.Config.User}}' revka# Expected: 65534:65534
# Run hardened: read-only root, writable workspace volumedocker run --read-only -v /path/to/workspace:/workspace revka gatewayCI enforces that the container does not run as root, that the runtime stage uses
the :nonroot variant, and that an explicit numeric USER directive exists.
For deployment guidance, see
Docker, Compose & one-click PaaS.
Please report security vulnerabilities responsibly — do not open a public GitHub issue.
Open a private advisory via GitHub Security Advisories, or use GitHub private vulnerability reporting to reach the maintainers.
Include a description, reproduction steps, an impact assessment, and a suggested fix if you have one.
Expect an acknowledgment within 48 hours, an assessment within one week, and a fix within two weeks for critical issues.