security

Defence in depth, by default.

openclaw-rs treats security as a runtime property, not a recommendation. Input validation, sandboxing, encrypted credentials, fail-secure errors, and audit logging are wired in before anything else. Five layers, independently load-bearing, deliberately redundant.

threat surface

What the runtime defends against

Each row pairs a vector with the layer that catches it. Multiple layers catch most vectors — that's the point.

Oversized payloads (DoS) Hard limits at boundaries

100 KB max message, 50 MB max attachment, 10-attachment cap, JSON depth 32.

Command injection via tools Platform sandbox

bubblewrap (Linux), sandbox-exec (macOS), Job Objects (Windows). Three levels: None / Relaxed / Strict.

Secret exfiltration Redaction by type

ApiKey wraps SecretBox<str>; Debug/Display always print [REDACTED]. Scrubbed in tracing spans.

Credential theft at rest AES-256-GCM + Argon2id

Encrypted credential store with 0600 file permissions; nonce per record.

Path traversal in file ops Validated path handling

validate_path() rejects `..` segments, null bytes, and paths outside the workspace mount.

Rate-limit abuse Per-client buckets

Default 60 requests/min per client and 30 messages/min per session via tower middleware.

Untrusted plugin code Out-of-process IPC

TypeScript plugins run in their own process; communicate over nng with bounded JSON-RPC messages.

Unsafe Rust Forbidden

#![forbid(unsafe_code)] across every workspace crate.

Layer 1 — Input validation at every boundary

Every external byte hits a validator before anything else looks at it.

  • Message size: 100 KB hard cap.
  • JSON depth: 32 levels.
  • Attachment size: 50 MB per attachment.
  • Attachment count: 10 per message.
  • Null bytes, control characters, malformed UTF-8 — rejected.

Layer 2 — Capability-based tool registration

Tools must be in the ToolRegistry to be invokable. The LLM can't conjure a tool that wasn't there.

Layer 3 — Platform sandbox

When a registered tool runs, it runs inside an OS-level sandbox.

[ LINUX ] bubblewrap (bwrap) --ro-bind /usr /usr --ro-bind /lib /lib --bind WS WS --unshare-net --unshare-pid --die-with-parent namespace isolation net + pid + mount workspace-only writes [ macOS ] sandbox-exec (version 1) (deny default) (allow file-read* /usr) (allow file-r/w WS) (deny network*) declarative profile Apple Seatbelt no network · workspace fs [ WINDOWS ] Job Objects CreateJobObject(...) JOBOBJECT_BASIC_LIMITS JOB_OBJECT_LIMIT_PROCESS _MEMORY · _CPU no child escape resource caps CPU + memory limits no UI access
Three platform backends · one capability-based policy (None / Relaxed / Strict).

Three policy levels:

  • None: no sandbox; only for trusted internal tools.
  • Relaxed: workspace-only writes, network allowed.
  • Strict: workspace-only writes, no network, isolated namespace.

Default in production: Strict. Whitelist looser policies per tool.

Layer 4 — Encrypted credential store

Master password → Argon2id KDF (64 MiB / 3 iterations / 4 lanes, 32-byte output) → AES-256-GCM with a fresh nonce per record. Files at 0600. Tag-authenticated, so any tampering fails the decrypt.

At runtime, secrets live inside ApiKey(SecretBox<str>)Debug and Display always print [REDACTED]; Drop zeroes the buffer.

Layer 5 — Fail-secure errors and audit logging

Every external-facing function returns Result<T, E>. There is no unwrap() on user input, no panic!() on parse failure. Errors return sanitised messages; the underlying cause is logged with full context internally via tracing.

Audited events:

  • Authentication attempts (success and failure).
  • Authorisation decisions.
  • Tool executions, with parameters.
  • Configuration changes.
  • Rate-limit triggers.

Rate limiting

Tower middleware enforces 60 requests/min per client at the gateway and 30 messages/min per session at the agent layer. Override in openclaw.json if your workload requires it.

Dependency hygiene

cargo-deny is part of CI. We reject crates with known security advisories, copyleft licenses (we ship MIT), and unmaintained flags without a reason. Preferred: rustls over openssl; aes-gcm + argon2 from RustCrypto; secrecy for sensitive types.

What you still own

The runtime is not your tool code. If you register a tool that does format!("rm [object Object]", user_input), no sandbox saves you from yourself — the command was authorised by your registered code.

Validate inside your tools too. Use serde to parse params into typed structures. Use validator or garde to constrain fields. Never unwrap() on a model-supplied value.

Responsible disclosure

Security issues should not be filed as public GitHub issues. See docs/SECURITY.md in the repo for the disclosure address and PGP key.

read more

Two long-form pieces on the security model.

The zero-trust runtime piece and the AES-GCM credential store piece both go deeper than this page.