Security Model (Draft)
Doc status: Draft — normative for v0.1 where explicitly labeled. Last updated: 2025‑11‑02.
Sandbox & Capabilities (normative)
- Agents execute inside WASM/WASI sandboxes hosted by Wasmtime with capability-based host calls.
- Capability manifests (
policy.caps) are deny-by-default. Operators may only remove grants via overrides. - Capability families (normative set):
fs(scoped path lists)net(hostname allowlists; off by default)timekb_read/kb_write(domain lists, e.g.,contacts,artifacts)secrets(SecretStore IDs)model(broker usage)exec(disabled in v0.1)
- The runtime enforces capability checks at hostcall boundaries and records
denials as
cap.auditledger events (and structured logs) whensecurity.caps.audit_on_denyistrue(default). Operators may also enablesecurity.caps.audit_on_allowto persist allow decisions for high-scrutiny agents. - Implementation status: the
runloop-runtimecrate embeds Wasmtime, enforces capability checks for every exposed hostcall, and records denials ascap.auditevents via the knowledge base (seecrates/runtime/tests/capabilities.rs).
Secret Handling (normative)
- Secret material never lives in the POG; only opaque
secret_idreferences or hashed markers are stored. Hostcalls return raw values by default for backward compatibility. To opt into handle-only mode (rlsec_<...>), setsecurity.testing.expose_raw_secrets = false(RUNLOOP__SECURITY__TESTING__EXPOSE_RAW_SECRETS=0). Default remainstruefor now and will flip to handle-only in a future breaking release. - Default provider:
security.secrets.provider = "stub"= env-first + in-memory (EnvThenStore). Other providers:envsecret-service(DBus Secret Service; currently stubbed/best-effort; skipped inauto)pass(pass show runloop/<secret_id>, first line)age(file store undersecurity.secrets.root, default~/.runloop/secrets; master key at<root>/master.agekey0o600. Current implementation reads plaintext.agefiles; encryption TODO.)autoprobes pass → age → env+store (secret-service skipped until wired).
- TTL:
security.secrets.default_ttlsets cache max-age; expired entries are discarded and refreshed. Stale secrets are never served. - CLI (
rlp secrets put/get/list/delete) is planned; until then, provision secrets through the chosen backend directly. - Overrides may only reference existing secret IDs; agents cannot read arbitrary secrets without explicit capability grants.
Fail-fast secret validation
Agent launch validates that all declared secrets (in policy.caps) can be
resolved before loading the WASM module. If any secret cannot be resolved:
- Default behavior (
security.testing.allow_missing_secrets = false): agent launch fails immediately withError::SecretsMissing, listing the missing secret IDs. - Development override (
security.testing.allow_missing_secrets = true): agent launch proceeds with a warning. Use only for local development/testing.
Environment variable overrides:RUNLOOP__SECURITY__TESTING__ALLOW_MISSING_SECRETS=1RUNLOOP__SECURITY__TESTING__EXPOSE_RAW_SECRETS=1
This validation runs after secret IDs are pre-registered with the provider
(allow()), so stub/fixture providers that rely on pre-registration work
correctly.
Provenance & Audit (normative)
- Every agent message carries RMP provenance metadata (
model,provider,parameters,tooling). - All writes into the POG ledger (
events.sqlite) include BLAKE3 content hashes and source identifiers. - Structured JSON logs include
trace_id,opening_id,agent_id; redaction filters scrub secrets or PII patterns before sink. - Knowledge Base redacts PII (emails) at read time unless the caller holds
kb_read.contacts_raw. Masking uses a salted deterministic token so joins still work; privileged unmasking is audited and gated by config (kb.redaction.allow_unredacted_admin). - Optional Ed25519 signatures protect message integrity when crossing trust
boundaries or when
security.require_signed_messages = true.
Threat Model (v0)
Assumptions
- Host OS (Debian 12) is trusted and kept patched.
- Agents are untrusted code but must pass the WASM validator.
- Operators can inspect and reset the environment; hardware physical security is out of scope.
In-scope threats
- Malicious or compromised agent attempting to exfiltrate data outside granted capabilities.
- Supply-chain tampering of agent bundles (mitigated via manifest signatures + SBOM).
- Secrets disclosure through logs or unredacted artifacts.
- Privilege escalation via hostcall misuse.
Mitigations
- WASM sandbox with constrained syscalls; hostcalls check capability bitsets each invocation.
- Agent bundles are signed;
runloopdverifies signatures before install/launch. - Runloopd enforces outbound confirmation (
confirm_external_actions) and tripwires (network/FS volume thresholds). - Structured logging + telemetry scrubbing; security tests include fuzzing denied syscalls.
Out-of-scope (v0)
- Kernel-level exploits or side-channel attacks in Wasmtime/CPU.
- Multi-tenant isolation across different Unix users or hosts.
- Compromise of external LLM providers or user-supplied secrets outside Runloop’s control.
Package trust & signatures (normative)
- Agent bundles must ship an Ed25519 signature over
manifest.toml(canonical form) and referenced digests. - Trust anchors live in
~/.runloop/trust-policy.toml; install/launch flows refuse bundles without a matching, non-revoked key. - Capabilities permitted are constrained by trust rules (see
docs/ops.mdoverall policy). - First-party release keys rotate via signed keyset files; runtime caches latest keyset hash to detect downgrade attacks.
Telemetry & Privacy (informative)
- Telemetry is opt-in; default is local-only.
- When enabled, OTLP exporters remove
secret_id, raw prompt text is hashed, and cost metrics are aggregated per opening.