# OpenClaw Permissions & Control Model, Explained

How OpenClaw controls tool execution: the exec security/ask settings and their permissive default, allowlist mode's approval binding, per-agent and per-sender tool policy, the sandbox, plugin hooks with requireApproval, and the security audit command.

# The OpenClaw control model, explained

[OpenClaw](https://github.com/openclaw/openclaw) is the most-starred project in the agent ecosystem, and it's a different animal from the coding CLIs on [our comparison](/controls): 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](/integrations/openclaw).*

## The exec gate, and its default

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

```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](/blog/policy-recommendations-for-common-agent-archetypes). 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](/blog/interactive-vs-autonomous-the-empty-chair-test) 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](/integrations/openclaw) 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.

## Frequently asked questions


