Skip to content
Agentic Control Plane

The OpenClaw control model, explained

OpenClaw is the most-starred project in the agent ecosystem, and it’s a different animal from the coding CLIs on our comparison: a personal-assistant gateway that fronts messaging channels and runs tools on a host that’s usually unattended. That shape makes its control model unusually interesting — it has controls the coding agents lack (per-sender policy, pairing, an audit command) and a default posture that assumes more trust than any of them.

This page covers OpenClaw’s own controls. For wiring ACP into OpenClaw, see the install guide.

The exec gate, and its default

Execution control is two settings in ~/.openclaw/openclaw.json:

{ "tools": { "exec": { "security": "full", "ask": "off" } } }

security is full (host exec), allowlist (patterns like "git *", "npm *"), or deny. ask is always or off. The default is full + off — every command runs on the host, nothing prompts. The docs are candid that this is intentional single-operator UX: “guardrails for operator intent, not hostile multi-tenant isolation.” Respect the candor, and read it literally: the default configuration of the ecosystem’s most-starred agent is the configuration every other harness calls its escape hatch. The hardened baseline the docs themselves recommend is security: "deny" (or allowlist) with ask: "always" — if your OpenClaw is reachable by anyone but you, start there.

Allowlist mode’s approval binding

Where OpenClaw has invested hardest — and where every harness should be taking notes — is making an approval mean what it said:

  • Approvals bind the exact request context; the gateway stores a canonical run plan and rejects post-approval edits to the command or cwd.
  • Approved interpreter scripts (bun/deno run operands) are bound to on-disk snapshots — rewrite the script after approval and the run is denied.
  • strictInlineEval forces approval for interpreter inline forms (python -c, node -e, …); any heredoc always requires approval; an interpreter command that can’t be resolved to exactly one local file is denied.
  • Approval prompts now detect invisible-Unicode obfuscation in what they display — an attack class most harnesses haven’t considered: the string the human reads and the string the shell runs can differ by characters nobody can see.
  • Approval timeouts fail closed (part of a 2026 hardening wave that also moved transcript, sandbox, and MCP paths to fail-closed).

This is the deepest thinking about time-of-check/time-of-use in any harness’s approval flow. The approval choices are the familiar allow-once / allow-always / deny; allow-always accumulates standing grants that live in local config, with the usual caveat that nothing reviews what they’ve quietly become.

Tool policy by agent — and by sender

Because OpenClaw fronts messaging channels, it has a policy axis the coding CLIs don’t need: who is asking. Global and per-agent tools.allow/tools.deny, tool profiles (messaging, minimal), tools.toolsBySender, and an elevated exec gate with its own allow-from list. One rule is hard-coded: non-owner senders never get the cron or gateway tools — the harness understands that scheduling and self-reconfiguration are the dangerous verbs, a lesson we’ve argued from the policy side. Unknown DMs go through explicit pairing approval. This is real identity-aware policy — scoped to message senders; tool calls themselves still aren’t attributed to an identity the way a workspace ledger would.

The sandbox

Present, capable, off by default: agents.defaults.sandbox.mode: "off" | "all", per-agent or per-session scope, workspaceAccess: "none" | "ro" | "rw" (default none), docker bind mounts validated against a denylist (with a dangerouslyAllowExternalBindSources override that earns its name). Sandbox network defaults to none; elevated exec bypasses sandboxing; host=sandbox fails closed when no runtime exists. Subagents can be forced to sandbox with sessions_spawn sandbox: "require" (default inherit).

The hook surface

Two systems, easy to confuse, only one of which can control tools:

  • Plugin hooks (in-process TypeScript, registered from a plugin entry): before_tool_call / after_tool_call plus agent, message, and gateway lifecycle events. before_tool_call can return { block: true } — terminal, stops lower-priority handlers — or a requireApproval result whose resolutions are the typed union allow-once | allow-always | deny | timeout | cancelled. Typed, composable, and it can drive the native approval flow: a genuinely good interception point. Two catches from the changelog: only the typed hook runner dispatches these events (the legacy registerHook path silently doesn’t), and before_tool_call existed for a while before it was actually wired into the execution pipeline — verify your hook fires with a test call after upgrades, not by reading the docs.
  • Internal hooks (operator shell scripts): command and gateway lifecycle only — no tool events (the request for tool:before/tool:after is an open issue). If someone tells you they’ve “added a hook” to control OpenClaw tools, ask which system they mean.

Plugin installation itself is gated (plugins.allow, security.installPolicy) — the harness treats its own extension mechanism as a control surface, which is more than most do.

The audit command

openclaw security audit (with --deep and --fix) runs named checks against your live config — flagging, among others, exactly the permissive defaults above (tools.exec.security_full_configured, unauthenticated gateway binds). A harness that ships a tool to critique its own configuration is rare and commendable. Note what it audits: the configuration, not the history — session transcripts (JSONL per agent, always-on redaction) remain the run’s own account of what happened, and nothing records decisions off-machine.

Where the native model ends

OpenClaw’s control surface is broad and visibly battle-tested — approval binding and sender-axis policy are ahead of the coding CLIs — but the shape of the gaps is the ecosystem’s standard four, sharpened by the fact that OpenClaw’s whole job is to run unattended:

  • The default posture and the deployment model point in opposite directions. An always-on, message-reachable agent is the empty-chair case par excellence, and the default config asks nothing and denies nothing.
  • Policy is one host’s JSON. Everything above lives in openclaw.json on the machine — no fleet view, no reconciliation, and the config is writable by whatever can write files there.
  • The record is self-authored. Transcripts plus a config auditor, but no independent per-decision ledger, and standing allow-always grants accumulate outside any review surface.

The pairing that works: adopt the hardened baseline, keep the approval binding — it’s the best in the business — and put workspace policy, decision recording, and standing-grant review in a layer off the host. Our plugin rides before_tool_call: workspace rules return block or requireApproval through OpenClaw’s own typed flow, and every decision — including every allow-always a human grants — lands in a ledger you can actually read next quarter.