10 KiB
description, icon
| description | icon |
|---|---|
| Trust boundary for the autonomous core - autonomy / risk policy, pluggable sandbox backends (Docker, Bubblewrap, Firejail, Landlock, Noop), audit log, encrypted secret store, public-bind / pairing guard, and the redact() helper. | shield-halved |
Security (src/openhuman/security/)
src/openhuman/security/ is the trust boundary for the autonomous core. It owns the autonomy / risk policy that decides whether a given tool call is allowed, the pluggable sandbox backends that confine those calls when the host supports it, the append-only audit log of every agent action, the encrypted secret store, the pairing guard that gates public binding of the RPC server, and the redact() helper every other domain uses to keep logs free of plaintext credentials.
It does not own:
- The cross-domain
EncryptionEngine, which lives insrc/openhuman/encryption/. - Per-channel credential storage, which lives in
src/openhuman/credentials/.
This module is the place to look first when asking "is this agent action allowed, and if so, how is it confined?"
Public surface
| Item | File | Purpose |
|---|---|---|
SecurityPolicy |
policy.rs |
Assembles runtime policy from AutonomyConfig + workspace dir. |
AutonomyLevel (Supervised / SemiAutonomous / Autonomous) |
policy.rs |
Three-step autonomy ladder. |
CommandRiskLevel, ToolOperation, ActionTracker |
policy.rs |
Risk classification + per-session counting. |
Sandbox trait, NoopSandbox |
traits.rs |
The pluggable sandbox abstraction; every backend implements Sandbox. |
create_sandbox(&SecurityConfig) -> Arc<dyn Sandbox> |
detect.rs |
Picks the best backend available on the host at runtime. |
pub mod docker / bubblewrap / firejail / landlock |
(siblings) | Per-backend implementations of Sandbox. |
SecretStore |
secrets.rs |
XOR / OS-keychain encrypted secret persistence with round-trip helpers. |
AuditLogger, AuditEventType, AuditEvent, Actor, Action, ExecutionResult, SecurityContext, CommandExecutionLog |
audit.rs |
Append-only audit trail. |
PairingGuard, constant_time_eq, is_public_bind |
pairing.rs |
Pairing-token check before binding the RPC server publicly. |
redact(value: &str) -> String |
core.rs |
Uniform 4-char-prefix redaction for logs. |
security_policy_info() -> RpcOutcome<serde_json::Value> |
ops.rs |
RPC handler for the doctor / settings UI. |
Sandbox backend selection
detect::create_sandbox walks a preference list and returns the first available backend on the host. The exact order is encoded in detect.rs; in practice it favours the strongest available isolation:
┌──────────────┐
SecurityConfig ─►│ create_sandbox│
└──────┬───────┘
│ probes
├─► Docker (best isolation; needs daemon)
├─► Bubblewrap (Linux user-namespace sandbox)
├─► Firejail (Linux setuid sandbox)
├─► Landlock (Linux LSM; in-process)
└─► Noop (last resort; logs only)
The agent never sees the choice; it just calls into Sandbox::run(...) and the active backend handles the rest. Every backend lives in a sibling file (docker.rs, bubblewrap.rs, firejail.rs, landlock.rs); the noop fallback is in traits.rs.
Autonomy ladder
AutonomyLevel is a three-step ladder that controls how aggressively the policy gates tool calls:
- Supervised: every higher-risk tool call requires an explicit approval round-trip.
- SemiAutonomous: low / medium-risk tool calls flow through; higher-risk ones still approval-gate.
- Autonomous: the policy lets the agent run unattended within budget and risk caps.
CommandRiskLevel + ToolOperation classify a given tool call; ActionTracker keeps the per-session counts that the policy compares against caps. The agent harness asks SecurityPolicy for a decision before every executable tool dispatch.
Audit log
audit.rs writes an append-only stream of AuditEvents under the workspace dir. Every executable tool call lands here with its Actor (agent / user), Action, ExecutionResult, and the SecurityContext (autonomy level, sandbox backend, etc.) it ran under. The log is the post-hoc story of what the agent did and why it was allowed.
Pairing guard
PairingGuard (in pairing.rs) stands between the RPC server and any attempt to bind to a non-loopback address. is_public_bind detects the dangerous case; PairingGuard requires a constant-time-compared pairing token (constant_time_eq) before such a bind is permitted. This is the iOS / LAN-companion pairing flow's defence against an unpaired peer attaching to the desktop core.
Secret store
SecretStore (in secrets.rs) persists per-key secrets with at-rest encryption. On supported platforms the encryption key comes from the OS keychain; otherwise it falls back to a workspace-local XOR scheme (which is obfuscation, not security, and is documented as such in the source).
redact()
redact(value) returns a uniform 4-char-prefix string (e.g. "sk-a" -> "sk-a…") for use in logs and error messages. Use it whenever a secret, credential, token, or PII string is about to be formatted into a log:: / tracing:: call. Other domains call it directly: credentials/, webhooks/, composio/, the integration adapters.
Layout
| Path | Role |
|---|---|
policy.rs, policy_tests.rs |
SecurityPolicy, AutonomyLevel, risk classification, action tracking. |
traits.rs |
Sandbox trait + NoopSandbox fallback. |
detect.rs |
create_sandbox: best-available-backend selection. |
docker.rs / bubblewrap.rs / firejail.rs / landlock.rs |
Per-backend Sandbox implementations. |
core.rs |
redact() + small shared helpers (has its own #[cfg(test)] mod tests). |
audit.rs |
Append-only audit log types. |
secrets.rs, secrets_tests.rs |
SecretStore + round-trip tests. |
pairing.rs, pairing_tests.rs |
PairingGuard + constant-time helpers. |
ops.rs |
RPC handler (security_policy_info). |
schemas.rs |
Controller schemas + handler dispatch. |
mod.rs |
Re-exports of the public surface above. |
Calls into
src/openhuman/config/:SecurityConfig,AutonomyConfigfor policy + sandbox selection.- OS-level sandbox tools:
docker,bwrap,firejail, Landlock syscalls (per backend). - Workspace filesystem, for the audit log and secret store.
Called by
src/openhuman/cron/scheduler.rs: wraps shell jobs inSecurityPolicy::from_config.src/openhuman/tools/local_cli.rs,tools/ops.rs, and mosttools/impl/{system,network,memory,agent}/*.rs: every executable tool consultsSecurityPolicy.src/openhuman/tools/impl/network/{curl,http_request,composio}.rs: risk-classify outbound calls.src/openhuman/memory/tools/{store,forget}.rs: sensitive-write tracking.src/openhuman/agent/tools/delegate.rs: sub-agent dispatch goes through the autonomy gate.src/openhuman/credentials/: usesSecretStoreandredact.
Tests
- Unit:
pairing_tests.rs,policy_tests.rs,secrets_tests.rs. core.rshas its own#[cfg(test)] mod tests, which round-tripsSecretStoreencrypt / decrypt,redact()cases,PairingGuarddefaults.- Sandbox-backend smoke tests: each backend file has its own
#[cfg(test)]blocks where the binary is available on the host.
Related
security/README.md: authoritative internal-audience overview this page mirrors.- Architecture overview: wider system context.
- Agent Harness: where
SecurityPolicyis consulted on every tool dispatch.