Lesson 3 said it plainly: prompt-injection guardrails are soft; the real fence is mechanical. This lesson builds that fence — the tool policy, the exec-approval ladder, the allowlist, and the sandbox: the layers that decide whether the agent's request to run a command actually executes.
system.run that lets it grep your codebase lets it delete your home directory. So OpenClaw stacks a mechanical fence — tool policy, an exec-approval ladder, an allowlist, and a sandbox — that decides, on every single request, whether the command actually executes.
Up to now the agent has been mostly a mouth: it reads messages, thinks, and writes replies. This lesson is about its hands. A tool is any ability the agent can invoke to change or sense the world outside the conversation: run a shell command (exec), drive a browser, search the web, read and write files. Tools are what turn a chatbot into an agent. They are also exactly where it can hurt you.
Hold the two halves of that together, because the whole lesson lives in the tension. The power of a tool and its risk are the same thing seen from two sides. A shell that can run rg to search your project can also run rm -rf ~ to erase it. A browser that can read a docs page can also read your logged-in bank session. You cannot have the useful half without owning the dangerous half — so you put a fence around the dangerous half.
OpenClaw builds the fence in three enforcement layers, and naming them now gives you the map for the whole lesson. First, a tool policy decides whether a tool is even allowed (an allow/deny list). Second, for the riskiest tool — exec — an exec-approval ladder decides whether a command runs automatically, prompts a human, or is blocked outright. Third, a sandbox isolates whatever does run, so even an allowed command can only touch a fenced-off corner of the machine. Policy, then approval, then sandbox. Each chapter fills one in.
Keep one frame in mind throughout: every layer here is designed to fail safe. When something is ambiguous — no UI is reachable, the command doesn't match, the config and the host disagree — the answer defaults to "deny," not "allow." A fence that opens when it's confused is not a fence.
Of all the tools, exec — running a shell command on the host — is the sharpest. So OpenClaw gives it a dedicated dial: a security mode, set at tools.exec.security, that picks one of five behaviors. Think of the five as a ladder from strictest to loosest. As you climb, the agent gets more freedom and you get less protection. Each rung is a deliberate point on that trade.
Notice the shape: at the bottom (deny) nothing runs; at the top (full) everything runs; the three rungs in between are where the interesting work happens, because they treat a matched command and an unmatched command differently. The allowlist is the hinge the whole ladder turns on — we devote the next chapter to it.
auto), but the host — the node that would actually run the command — carries its own default mode, and the gateway enforces whichever of the two is stricter. If your config says full but the host's default is allowlist, the effective mode is allowlist. You can always tighten the fence from either side; you can never loosen it past what the host permits. Two locks, and the stronger one wins.
Below, the ladder made visible. A command sits on the left; the five modes are rows on the right. Flip the command between allowlisted and not, and watch each rung light up its outcome — green for run, amber for prompt-a-human, red for deny. The point to feel: deny and full ignore the allowlist entirely (they're flat), while allowlist, ask, and auto all hinge on whether the command matched.
Each row is a security mode. The pill shows what that mode does with the current command: run, prompt, or deny. Toggle whether the command is on the allowlist and watch the middle three rungs flip while deny and full stay put.
rg pattern is currently allowlisted.Let's build the ladder in code — the single function that, given a mode and whether the command matched, returns one of run, prompt, or deny. It is small, but the shipped version has the exact bug that ruins fences: it rubber-stamps everything.
tools.exec.security to full, but the host node's default mode is allowlist. What mode does the gateway actually enforce?The ladder hinges on one question: is this command on the allowlist? In Chapter 1 we faked the answer with "is the first word in a list." Real life is far more slippery, because a command is not a single word and an attacker will exploit every gap. So OpenClaw's allowlist is precise about what "matches" means. Five rules, each closing a specific hole.
rg), an absolute path (/opt/homebrew/bin/rg), or a glob (~/Projects/**/bin/x). The command's binary must glob-match an entry.rg matches when you type rg (resolved via your PATH), but not when you run ./rg or /tmp/rg — a relative or absolute path is a different thing and must be matched by a path entry. This stops an attacker dropping a malicious rg in a local folder.argPattern restricts arguments. An entry can carry a regex the joined arguments must match. {pattern: 'python3', argPattern: '^safe\\.py$'} allows python3 safe.py but denies python3 other.py. The binary alone is not enough.a && b | c is three commands glued together. Each segment must independently satisfy the allowlist; one bad segment denies the whole chain. rg foo && rm -rf / is denied because rm isn't approved — even though rg foo is.strictInlineEval blocks inline code. Even when an interpreter is allowlisted, this flag denies its inline-eval forms — python -c, node -e, osascript -e — because inline code is an open door: python3 -c "..." can run anything. You allowlisted the interpreter to run scripts you trust, not arbitrary inline programs.Sit with why each rule exists, because each is a patched attack. Globs without the bare-name rule would let ./rg impersonate the real rg. An interpreter without argPattern would let python3 anything.py through once python3 was approved for one safe script. A chain without the all-segments rule would let an attacker smuggle rm -rf / behind a benign rg. And strictInlineEval exists because python3 -c turns "I approved python" into "I approved arbitrary code." The allowlist is a list of scars.
Let's implement the matcher itself — the function that takes a full command string and decides, segment by segment, whether the allowlist permits it. Getting the chain and inline-eval rules exactly right is the difference between a fence and a sieve.
python3 is on the allowlist. By the allowlist rules, what happens to python3 -c "import os; os.system('rm -rf ~')" when strictInlineEval is on?In ask and auto modes, a command that misses the allowlist doesn't die — it pauses and asks a human. That pause is a small protocol, and it has one subtle property that makes it actually safe rather than theater. Let's walk it.
When a miss happens, the gateway broadcasts an exec.approval.requested event to every connected operator client — your desktop app, the CLI, the web UI. The event carries what's being asked: the exact command, the working directory, who's asking. A human sees it and clicks. The UI sends back an exec.approval.resolve with their verdict. If approved, the gateway forwards the run to the node host that will execute it. If denied, that's the end — denied approvals are terminal; the command does not run, and there is no retry of the same request.
Why does this matter so much? Without canonical binding, a clever attack is trivial: get the human to approve a harmless-looking ls, then swap in rm -rf ~ before the run is forwarded — same approval, different command. By pinning the approval to the exact command, cwd, agent, and session, OpenClaw makes the approval non-transferable. The thing the human saw and the thing that runs are guaranteed identical, or nothing runs. Consent is for one specific act, not a blank check.
ask/auto → gateway broadcasts exec.approval.requested (command, cwd, agent, session) → operator UI shows it → human resolves with exec.approval.resolve → if approved AND the canonical tuple still matches, forward to the host → run. If denied, terminal — no run, no retry. If the tuple changed, reject the forward even though approval was granted.
So the approval flow is not "are you sure?" It is a cryptographically-careful handshake about one specific command in one specific place. The pin is the whole point. We'll see the same instinct — consent attached to an exact request, not a category — echo through the precedence rules in Chapter 6.
exec.approval.requested for ls /tmp. Before the run is forwarded, the command is changed to rm -rf ~. What happens?The ask mode has its own little dial — when to ask — set at tools.exec.ask. It takes three values, and the spread covers the natural range of paranoia.
ask.Now the hard question: what happens when a prompt is required but no human is reachable? Your phone is asleep, the desktop app is closed, the UI is down. The system can't just hang forever, and it certainly can't quietly run the command. That decision is tools.exec.askFallback, and its default is deny. If there's no one to ask, the safe answer is no. Fail closed, every time.
tools.exec.security = full AND tools.exec.ask = off AND the host's askFallback = full. Miss any one and the fence reasserts itself — a stricter security mode gates the command, or ask stops to prompt, or askFallback denies when no one answers. Three independent locks, all of which must be open. You cannot YOLO by accident.
This is a recurring design principle worth naming: dangerous states should require multiple, independent confirmations. A single boolean that disables all safety is a single point of catastrophe — one bad config push, one fat-fingered toggle, and you're wide open. Spreading the requirement across three knobs in three layers means an accident in one place is caught by the other two. The fence has defense in depth even against you.
Think of askFallback as the answer to "the doorbell rang and nobody's home." A safe house doesn't open the door to whoever rings when the owners are out; it stays locked. askFallback: deny is that locked door. Setting it to full — "let anyone in when we're out" — is one of the three switches YOLO needs, and it's the one that should make you most nervous.
tools.exec.security = full and tools.exec.ask = off, but leave the host's askFallback at its default. Have you enabled "YOLO" (no-approvals) execution?The ladder and the allowlist decide whether a command runs. The sandbox decides where it runs — it isolates tool execution so that even an allowed command can only touch a fenced-off slice of the machine. The word to hold onto is blast radius: if something does go wrong, how much can it reach? The sandbox shrinks that radius.
off (run tools on the host directly), non-main (sandbox only non-main sessions — the default), or all (sandbox everything).agent (one container per agent), session (one per conversation), or shared (one shared by all). Tighter scope = stronger isolation between agents/sessions.none (no host files at all), ro (a read-only mount), or rw (read-write). This is the dial between "the agent can read my project" and "the agent can change my project."The default — non-main — is a thoughtful compromise. Your main session is your trusted foreground chat, where you're watching every step, so it runs on the host (full speed, full access). But non-main sessions — background jobs, spawned subagents, scheduled tasks running while you're not looking — are exactly the ones you can't supervise, so they get boxed up. Sandbox the unattended; trust the watched.
Let's build the precedence-aware decision that ties tool policy and sandbox together — the function that, for a given tool request, decides whether it's allowed, whether it's sandboxed, and the write-gate that workspaceAccess: ro enforces. This is also where Chapter 6's headline rule first appears in code.
mode is non-main (the default). A background subagent session and your foreground main session both run a command. Which one is sandboxed?You now have three mechanisms — tool policy, exec approval, sandbox — and the natural next question, the one you'll actually ask at 2 a.m. when something doesn't run, is: which one stopped it? The answer is a strict order of evaluation, and getting that order right is what makes the fence reasonable to debug.
This ordering isn't arbitrary — it's the only one that's safe. If the sandbox were checked first, you could imagine a denied tool slipping through because "it's sandboxed, so it's fine." But a denied tool is denied because you never want it to run at all, sandboxed or not. Policy is a statement of intent ("this tool is off-limits"); the sandbox is a statement of containment ("if it runs, box it"). Intent comes first. You decide whether before you decide where.
tools.elevated. There is exactly one way for an authorized request to escape the sandbox: a tool listed in tools.elevated, invoked by an authorized sender, runs on the host even when the sandbox would otherwise box it. This is a deliberate, narrow hole for trusted system-administration tools that genuinely need host access. But note where it sits in the pipeline: elevated escapes the sandbox gate — it does not escape the tool policy gate. A tool that is both elevated and in tool_deny is still denied. Escaping containment is not the same as escaping the decision to forbid.
So the full picture is a funnel with one labeled side door. A request enters at tool policy: if denied, it dies here, full stop — even elevated can't save it. If allowed, it reaches approval: in ask/auto, a miss waits for a canonically-pinned human yes. If approved, it reaches the sandbox: boxed unless the mode says otherwise or it's elevated. Three gates, one order, one side door — and now "why was this blocked?" has a deterministic answer you can trace.
tools.elevated (escapes the sandbox) and the tool deny policy. What happens when the agent invokes it?We've spent the lesson on exec because it's the sharpest tool, but the fence is for all capability that reaches into the world — and two more tools deserve a careful look, because their danger is quieter than a shell command's.
First, browser automation. When the agent drives a browser, it isn't using a fresh, anonymous tab — it can act inside a logged-in profile. That profile may hold your authenticated bank session, your email, your cloud console. So a browser tool isn't "read a public web page"; it's potentially "act as you, everywhere you're signed in." The right mental model: treat the browser profile as a secret, as sensitive as a password file. An agent that can drive your logged-in browser can do anything you can do in it — and a prompt-injected page might try to make it.
Second, web search. It looks innocuous — just fetching results — but it spans many providers, each an external service you're handing a query to, and each a channel through which untrusted text flows back into the agent. That returned text is a classic prompt-injection vector (Lesson 3's territory): a search result can contain instructions aimed at your agent. So web search is sensitive twice over: outbound (your queries leave your machine to third parties) and inbound (untrusted content returns and may try to steer the agent).
exec is just the loudest example. The discipline is uniform: power that touches the world is gated, period.
This is why the three-layer fence is framed around tools in general, not just exec. The ladder and allowlist are exec-specific because exec needs the finest control, but tool policy (allow/deny), the sandbox, and the precedence order apply to every tool. Browser and search are the reminder that the agent's reach is wider than a shell — and the fence has to be just as wide.
Here is the whole fence in one diagram. A command request enters on the left and flows through three gates — tool policy → exec approval → sandbox — and stops at the first one that says no. Flip the knobs and watch where it stops and why: change the exec mode, toggle whether the command hits the allowlist, deny the tool outright, set the sandbox mode, and switch the workspace to read-only. The precedence rule is highlighted as you go: a tool-policy deny beats everything, and elevated escapes only the sandbox.
The request flows left to right through tool policy, exec approval, and sandbox. A gate glows pass, prompt, or stop. Toggle the knobs and watch the verdict and the reason update.
argPattern, all-segments-of-a-chain, and strictInlineEval (Ch 2). The approval flow is canonically pinned to one command/cwd/agent/session (Ch 3). YOLO needs all three of security-full, ask-off, askFallback-full (Ch 4). The sandbox has mode/scope/workspaceAccess and boxes only tool execution, not the gateway (Ch 5). Precedence: tool policy before sandbox; denied stays denied; elevated escapes the sandbox but not policy (Ch 6). And every outward-reaching tool — browser, search — gets the same fence (Ch 7).
node.invoke and system.run came from — the capability nodes whose exec this fence guards.