Rune Box docs

Security model

Rune Box reduces the blast radius of an autonomous agent; this page lists what it protects, what it does not, and what stays the operator's job.

Warning: Per-action policy and approval decisions apply only to calls through the governed tool APIs (REST, MCP, dashboard shell/file/code controls and SDK adapters). The interactive terminal, the optional browser IDE and commands started directly inside a sandbox get sandbox, network and resource controls, but Rune Box does not intercept each shell command or keystroke for a verdict. Use governed tool calls when you need per-command approval and signed verdict evidence.

To report a vulnerability, follow SECURITY.md. Disclose privately; do not open a public issue.

What Rune Box provides

Control What it does
Sandbox hardening Each sandbox (Citadel) is a container, a Pod or its own microVM (host.type = "microvm": Firecracker or libkrun) with its own workspace. Docker drops all capabilities and sets no-new-privileges, a --pids-limit and memory/CPU ceilings (RUNEWARD_SANDBOX_MEMORY, RUNEWARD_SANDBOX_CPUS, RUNEWARD_SANDBOX_PIDS; 0 disables). host.read_only, host.seccomp and host.apparmor tighten it further. Tiers and runtimes: isolation.md.
No host mounts copy_from copies into the sandbox and never mounts the host. RUNEWARD_COPY_FROM_ROOTS limits which host directories it may read.
Endpoint binaries An [[endpoint]] with binaries is usable only by those executables. The broker asks the in-sandbox supervisor which executable holds each connection and refuses anything else (reason binary). Enforced, not advisory, and fails closed. See Endpoint binaries.
In-sandbox confinement Landlock, seccomp and a non-root identity. See isolation.md.
Network controls Deny-by-default egress (Perimeter), enforced at L3 on both backends, and a credential broker that keeps secrets out of the sandbox. See isolation.md.
Control-plane auth runeward serve binds 127.0.0.1. A non-loopback --bind needs a token (--token / RUNEWARD_API_TOKEN) or RBAC, required on every request, and TLS (--tls-cert/--tls-key) unless you pass --allow-insecure-http behind a trusted TLS proxy. Request bodies are size-capped.
RBAC and OIDC RUNEWARD_AUTHZ_FILE defines principals with their own tokens, launchable profiles and approve/admin scopes. Non-admins see only their tenant's sandboxes. RUNEWARD_OIDC_ISSUER plus RUNEWARD_OIDC_AUDIENCE enable RS256 JWT verification, mapping only signed runeward_* claims.
HTTP hardening nosniff, frame denial, no-referrer and a restrictive Permissions Policy. API responses are no-store with a deny-all content policy.
Policy and approvals allow / deny / require-approval verdicts, with human approvals (Conclave). Each decision records who made it.
Budgets and guardrails Caps on wall-clock, exec count, egress requests and token/spend, plus retry-loop detection. Budgets (Rationing) max_tokens / max_cost_usd fail closed.
Signed audit trail An append-only, hash-chained, ed25519-signed ledger (Chronicle), verifiable offline, with optional streaming (RUNEWARD_AUDIT_WEBHOOK_URL / RUNEWARD_AUDIT_FILE) and an anomaly detector (RUNEWARD_ANOMALY_*). RUNEWARD_RECORD_TERMINALS=1 records governed terminals for runeward replay.
Agent group leases Agent group (Cohort) task claims return a short-lived HMAC-signed lease_token.
Durable state Atomic private state files. A run still active after a restart is closed as interrupted.
Live chat Read-only, opt-in, redacted and ownership-checked. The signed audit trail stays the authoritative evidence.
Kubernetes Pod Security Admission labels, ALL capabilities dropped, no privilege escalation (a supervised sandbox adds back SETUID, SETGID and CHOWN and runs as root until the supervisor drops to the agent, which the baseline standard allows and restricted does not), and an optional default-deny NetworkPolicy (RUNEWARD_K8S_NETWORK_POLICY). The validating webhook fails closed; the mutating default-profile webhook is best-effort.
Supply chain Releases are cosign-signed (keyless) with SBOMs. CI runs gosec, CodeQL, govulncheck, Trivy, image CVE scans and a DAST baseline; unsuppressed HIGH gosec results block releases.

Known limits

  • Not a hard isolation boundary. Rune Box is defense in depth. The default container backend shares the host kernel, so an escape through a kernel or runtime vulnerability is possible. For untrusted or adversarial workloads, set host.runtime_class to a sandboxed runtime (isolation.md) or use host.type = "microvm" (see Native microVMs), and, for the strictest cases, use a disposable host. Rune Box governs a cooperative-but-fallible agent; it does not cage code that is trying to break out.
  • Runtimes must be registered with your engine first. An unknown name fails creation; there is no silent fallback to the shared-kernel runtime. runeward runtime install gvisor --apply downloads and registers gVisor for Docker; Rune Box does not install Kata (runtime install kata --apply only registers an existing install with Docker, or applies a RuntimeClass on Kubernetes). runeward runtime check finds registered runsc/kata runtimes and runeward runtime guide prints setup steps.
  • Interactive sessions are not policy-gated per command or keystroke. The browser IDE is experimental, off by default, and gives workspace-equivalent interactive access.
  • Redaction is best-effort. Declared secrets are redacted from the ledger and common credential shapes are masked, but pattern matching cannot catch every custom format.
  • Broker secrets live in the egress sidecar in strict mode and on Kubernetes (cooperative mode on Docker/Podman keeps them in the control plane). The sidecar gets endpoints, resolved credentials and the per-sandbox CA key, never on a command line or in an environment variable:
    • Docker/Podman: a 0600 root-owned file the sidecar reads as root, deletes, then drops privileges. The sandbox shares only its network namespace.
    • Kubernetes: a per-sandbox Secret mounted only into the egress container.
    • Anyone who can read the sidecar's filesystem or memory, or Secrets in the sandbox namespace, can read those credentials: the container engine, node root, and Kubernetes principals with Secret read or pods/exec on the egress container. Use cooperative mode on Docker/Podman when credentials must stay on the control-plane host.
    • A require-approval request in the sidecar is refused; approval routing is not available there yet.
    • If the control plane restarts while a sandbox runs, its later sidecar decisions are recorded again only once the sandbox is woken.
  • In-sandbox confinement depends on the sandbox: a best-effort policy runs unconfined where the supervisor is absent, and without Landlock where the kernel lacks it (both recorded). On Kubernetes the Kata and gVisor paths have not been tested against a cluster yet.
  • The isolation probe detects misconfiguration, not a hostile image.

Native microVMs

With host.type = "microvm" each sandbox is its own Firecracker or libkrun VM, so a guest kernel or runtime exploit lands in that VM, not on the host kernel. What crosses the boundary:

  • Disks. The image disk is read-only and shared by VMs of that image; the guest writes into memory. The workspace is a per-sandbox disk image. The host never mounts either: export, snapshots and file reads go through the guest agent. Image layers are verified against their digests and unpacked, as an unprivileged user, without following links out of the staging directory.
  • No network device. The only way out is a vsock port mapped to a per-sandbox Unix socket served by the policy's egress proxy, so default = "deny" and the credential broker cannot be bypassed from inside, even by guest root. Non-HTTP traffic has no path, and the browser IDE is unavailable.
  • Guest agent. runeward-vmagent (PID 1) is inside the boundary, so guest root can replace it. That affects only that sandbox's exec, files and isolation evidence: the verdict is capped at vm, the host confirms the monitor process it launched, and egress is enforced on the host. Commands run as the sandbox user, not the agent's root.
  • Monitor. Runs as the Rune Box user with /dev/kvm access. Firecracker uses its built-in seccomp filters but not its jailer; libkrun runs in the runeward-krun helper. Neither gets a network device or shared host directory.
  • Secrets. Environment values reach the guest over the agent's vsock stream, never on the kernel command line or in a host file; broker credentials stay in the control plane.

Endpoint binaries

[network]
default = "deny"
enforce = "strict"

[[endpoint]]
name = "github"
host = "api.github.com"
protocol = "rest"
credential = "github"
binaries = ["/usr/bin/gh", "/usr/bin/git"]
  • What matches: the executable the kernel runs (/proc/<pid>/exe), by device and inode, against the listed paths as they were when the sandbox started. A listed symlink matches its target and a hard link matches; a copy elsewhere, or a file placed at a listed path after start, does not. Scripts are never identified by name. Listing an interpreter, a shell, env or busybox allows every program it runs, and runeward validate warns about it.
  • How it is enforced: an attribution helper started by the supervisor finds every process holding the connection and answers the broker with an HMAC-SHA256 tag bound to a fresh nonce. The broker forwards only if the answer verifies and every holder is listed. In mode = "audit" it records audit-would-deny and forwards. Each decision records binary and binary_enforced.
  • Where it works: Docker and Podman with strict egress and a container runtime (runc, crun). Without strict egress a policy with binaries is refused (and is an error in runeward validate); gVisor and VM runtimes are refused at create. On Kubernetes it is enforced with runc (verified on kind; the supervisor in the Pod starts the helper, and the sidecar's decisions reach the perimeter list and the audit trail); Kata and gVisor have not been tested. Native microVMs are not supported: the broker runs on the host and cannot reach the helper in the guest, so runeward validate reports an error and the create is refused.
  • binaries requires the supervisor even without [filesystem] or [process], so the agent needs a non-root identity ([process] run_as_user, host.user or an image USER).

Residual risks:

  • Binary identity is not code identity: an allowed interpreter runs any script, and an allowed dynamically linked binary can be made to load other code (LD_PRELOAD, plugins, git hooks). Prefer static, single-purpose binaries and pair binaries with request rules.
  • Attribution happens when the connection is accepted; a socket handed to another process later is not re-checked.
  • The agent can kill the helper (same uid), which makes every binary-restricted endpoint refuse until the sandbox restarts.
  • A holder the helper cannot inspect, such as an operator's root docker exec shell, makes the connection refuse.

Operator responsibilities

These are out of scope for Rune Box:

  • Security of the container runtime, host kernel and Kubernetes cluster. Keep them patched.
  • Trustworthiness of images in policies and of the agents and CLIs you run in a sandbox.
  • Secrets you place in policies.
  • Network exposure of runeward serve: token strength, TLS termination, and RBAC or OIDC instead of a shared token for teams.
  • Denial of service from workloads you grant large resource limits.

Operating safely

Warning: The audit ledger is single-writer. Give each running instance its own RUNEWARD_STATE_DIR. Two processes sharing one ledger permanently break the hash chain, and verification then reports tampering.

  • The terminal WebSocket enforces same-origin, and state-changing REST requests reject mismatched browser Origins.
  • Per-IP rate limiting defaults to 50 requests/sec (RUNEWARD_RATE_LIMIT, or off for trusted benchmarks).
  • Front the control plane with TLS in production.

In scope for reports

  • Sandbox escape to the host or another sandbox.
  • Bypass of the egress allowlist, policy engine or approval gates.
  • Audit-ledger forgery or tampering that verification would miss.
  • Path traversal or writes outside the workspace (for example tar-slip).
  • Auth flaws in the REST API, WebSocket terminal, browser IDE proxy or admission webhook.
  • Secret leakage in logs, the ledger or the dashboard.

Security reviews

Reviews on 10 and 19 September 2026 covered Runeeward/runeward and Runeeward/homebrew-tap. They fixed vulnerable dependencies (golang.org/x/crypto, google.golang.org/grpc, Alpine OpenSSL, js-yaml in the IDE images), added request-header deadlines to internal HTTP servers, restricted unauthenticated dashboard paths to an exact allowlist, pinned every GitHub Action to a commit, and enabled vulnerability alerts. The malware review found no unexpected executables or download-and-execute paths.

Note: Rune Box has since moved from Go to Rust, so those Go findings describe the earlier implementation. Rust dependencies are checked by cargo deny and cargo audit.

Written from docs/security-model.md, docs/security-review.md at commit 1ee29ff4ab36. Rune Box is open source under Apache-2.0.