2026-01-18 01:33:52 +00:00
---
2026-01-18 04:27:33 +00:00
summary: "Exec approvals, allowlists, and sandbox escape prompts"
2026-01-18 01:33:52 +00:00
read_when:
- Configuring exec approvals or allowlists
- Implementing exec approval UX in the macOS app
- Reviewing sandbox escape prompts and implications
2026-01-31 16:04:03 -05:00
title: "Exec Approvals"
2026-01-18 01:33:52 +00:00
---
2026-01-18 04:27:33 +00:00
# Exec approvals
2026-01-18 07:44:28 +00:00
Exec approvals are the **companion app / node host guardrail** for letting a sandboxed agent run
2026-01-18 04:27:33 +00:00
commands on a real host (`gateway` or `node` ). Think of it like a safety interlock:
commands are allowed only when policy + allowlist + (optional) user approval all agree.
2026-01-22 05:32:13 +00:00
Exec approvals are **in addition** to tool policy and elevated gating (unless elevated is set to `full` , which skips approvals).
2026-01-24 04:53:26 +00:00
Effective policy is the **stricter** of `tools.exec.*` and approvals defaults; if an approvals field is omitted, the `tools.exec` value is used.
2026-01-18 04:27:33 +00:00
If the companion app UI is **not available** , any request that requires a prompt is
resolved by the **ask fallback** (default: deny).
## Where it applies
Exec approvals are enforced locally on the execution host:
2026-01-31 21:13:13 +09:00
2026-01-30 03:15:10 +01:00
- **gateway host** → `openclaw` process on the gateway machine
2026-01-18 07:44:28 +00:00
- **node host** → node runner (macOS companion app or headless node host)
2026-01-18 04:27:33 +00:00
2026-02-24 01:35:40 +00:00
Trust model note:
- Gateway-authenticated callers are trusted operators for that Gateway.
- Paired nodes extend that trusted operator capability onto the node host.
- Exec approvals reduce accidental execution risk, but are not a per-user auth boundary.
2026-03-07 23:03:26 +00:00
- Approved node-host runs also bind canonical execution context: canonical cwd, pinned executable
path when applicable, and interpreter-style script operands. If a bound script changes after
approval but before execution, the run is denied instead of executing drifted content.
2026-02-24 01:35:40 +00:00
2026-01-22 23:07:58 +00:00
macOS split:
2026-01-31 21:13:13 +09:00
2026-01-21 16:48:31 +00:00
- **node host service** forwards `system.run` to the **macOS app** over local IPC.
2026-01-18 16:20:48 +00:00
- **macOS app** enforces approvals + executes the command in UI context.
2026-01-18 04:27:33 +00:00
## Settings and storage
2026-01-18 07:44:28 +00:00
Approvals live in a local JSON file on the execution host:
2026-01-18 04:27:33 +00:00
2026-01-30 03:15:10 +01:00
`~/.openclaw/exec-approvals.json`
2026-01-18 04:27:33 +00:00
Example schema:
2026-01-31 21:13:13 +09:00
2026-01-18 04:27:33 +00:00
```json
{
"version": 1,
"socket": {
2026-01-30 03:15:10 +01:00
"path": "~/.openclaw/exec-approvals.sock",
2026-01-18 04:27:33 +00:00
"token": "base64url-token"
},
"defaults": {
"security": "deny",
"ask": "on-miss",
"askFallback": "deny",
"autoAllowSkills": false
},
"agents": {
"main": {
"security": "allowlist",
"ask": "on-miss",
"askFallback": "deny",
"autoAllowSkills": true,
"allowlist": [
{
2026-01-23 18:59:59 +00:00
"id": "B0C8C0B3-2C2D-4F8A-9A3C-5A4B3C2D1E0F",
2026-01-18 04:27:33 +00:00
"pattern": "~/Projects/**/bin/rg",
"lastUsedAt": 1737150000000,
"lastUsedCommand": "rg -n TODO",
"lastResolvedPath": "/Users/user/Projects/.../bin/rg"
}
]
}
}
}
```
## Policy knobs
### Security (`exec.security`)
2026-01-31 21:13:13 +09:00
2026-01-18 04:27:33 +00:00
- **deny**: block all host exec requests.
- **allowlist**: allow only allowlisted commands.
- **full**: allow everything (equivalent to elevated).
### Ask (`exec.ask`)
2026-01-31 21:13:13 +09:00
2026-01-18 04:27:33 +00:00
- **off**: never prompt.
- **on-miss**: prompt only when allowlist does not match.
- **always**: prompt on every command.
### Ask fallback (`askFallback`)
2026-01-31 21:13:13 +09:00
2026-01-18 04:27:33 +00:00
If a prompt is required but no UI is reachable, fallback decides:
2026-01-31 21:13:13 +09:00
2026-01-18 04:27:33 +00:00
- **deny**: block.
- **allowlist**: allow only if allowlist matches.
- **full**: allow.
2026-01-18 01:33:52 +00:00
## Allowlist (per agent)
2026-01-18 04:27:33 +00:00
Allowlists are **per agent** . If multiple agents exist, switch which agent you’ re
editing in the macOS app. Patterns are **case-insensitive glob matches** .
2026-01-21 21:44:28 +00:00
Patterns should resolve to **binary paths** (basename-only entries are ignored).
2026-01-22 04:05:54 +00:00
Legacy `agents.default` entries are migrated to `agents.main` on load.
2026-01-18 01:33:52 +00:00
Examples:
2026-01-31 21:13:13 +09:00
2026-02-06 22:28:44 -08:00
- `~/Projects/**/bin/peekaboo`
2026-01-18 01:33:52 +00:00
- `~/.local/bin/*`
- `/opt/homebrew/bin/rg`
Each allowlist entry tracks:
2026-01-31 21:13:13 +09:00
2026-01-23 18:59:59 +00:00
- **id** stable UUID used for UI identity (optional)
2026-01-18 04:27:33 +00:00
- **last used** timestamp
2026-01-18 01:33:52 +00:00
- **last used command**
2026-01-18 04:27:33 +00:00
- **last resolved path**
2026-01-18 01:33:52 +00:00
2026-01-18 04:27:33 +00:00
## Auto-allow skill CLIs
2026-01-18 01:33:52 +00:00
2026-01-18 04:27:33 +00:00
When **Auto-allow skill CLIs** is enabled, executables referenced by known skills
2026-01-22 23:07:58 +00:00
are treated as allowlisted on nodes (macOS node or headless node host). This uses
`skills.bins` over the Gateway RPC to fetch the skill bin list. Disable this if you want strict manual allowlists.
2026-01-18 01:33:52 +00:00
2026-02-24 01:35:40 +00:00
Important trust notes:
- This is an **implicit convenience allowlist** , separate from manual path allowlist entries.
- It is intended for trusted operator environments where Gateway and node are in the same trust boundary.
- If you require strict explicit trust, keep `autoAllowSkills: false` and use manual path allowlist entries only.
2026-01-21 21:44:28 +00:00
## Safe bins (stdin-only)
`tools.exec.safeBins` defines a small list of **stdin-only** binaries (for example `jq` )
that can run in allowlist mode **without** explicit allowlist entries. Safe bins reject
positional file args and path-like tokens, so they can only operate on the incoming stream.
2026-02-22 12:57:53 +01:00
Treat this as a narrow fast-path for stream filters, not a general trust list.
Do **not** add interpreter or runtime binaries (for example `python3` , `node` , `ruby` , `bash` , `sh` , `zsh` ) to `safeBins` .
If a command can evaluate code, execute subcommands, or read files by design, prefer explicit allowlist entries and keep approval prompts enabled.
Custom safe bins must define an explicit profile in `tools.exec.safeBinProfiles.<bin>` .
2026-02-19 14:14:46 +01:00
Validation is deterministic from argv shape only (no host filesystem existence checks), which
prevents file-existence oracle behavior from allow/deny differences.
File-oriented options are denied for default safe bins (for example `sort -o` , `sort --output` ,
2026-02-23 23:55:28 +00:00
`sort --files0-from` , `sort --compress-program` , `sort --random-source` ,
`sort --temporary-directory` /`-T` , `wc --files0-from` , `jq -f/--from-file` ,
2026-02-21 19:13:53 +01:00
`grep -f/--file` ).
2026-02-19 14:07:43 +01:00
Safe bins also enforce explicit per-binary flag policy for options that break stdin-only
2026-02-21 19:13:53 +01:00
behavior (for example `sort -o/--output/--compress-program` and grep recursive flags).
2026-02-23 23:55:28 +00:00
Long options are validated fail-closed in safe-bin mode: unknown flags and ambiguous
abbreviations are rejected.
2026-02-21 19:24:23 +01:00
Denied flags by safe-bin profile:
<!-- SAFE_BIN_DENIED_FLAGS:START -->
- `grep` : `--dereference-recursive` , `--directories` , `--exclude-from` , `--file` , `--recursive` , `-R` , `-d` , `-f` , `-r`
- `jq` : `--argfile` , `--from-file` , `--library-path` , `--rawfile` , `--slurpfile` , `-L` , `-f`
2026-02-23 23:55:28 +00:00
- `sort` : `--compress-program` , `--files0-from` , `--output` , `--random-source` , `--temporary-directory` , `-T` , `-o`
2026-02-21 19:24:23 +01:00
- `wc` : `--files0-from`
<!-- SAFE_BIN_DENIED_FLAGS:END -->
2026-02-14 19:42:52 +01:00
Safe bins also force argv tokens to be treated as **literal text** at execution time (no globbing
2026-02-14 19:59:03 +01:00
and no `$VARS` expansion) for stdin-only segments, so patterns like `*` or `$HOME/...` cannot be
used to smuggle file reads.
2026-02-22 22:42:29 +01:00
Safe bins must also resolve from trusted binary directories (system defaults plus optional
`tools.exec.safeBinTrustedDirs` ). `PATH` entries are never auto-trusted.
2026-02-24 23:12:52 +00:00
Default trusted safe-bin directories are intentionally minimal: `/bin` , `/usr/bin` .
If your safe-bin executable lives in package-manager/user paths (for example
`/opt/homebrew/bin` , `/usr/local/bin` , `/opt/local/bin` , `/snap/bin` ), add them explicitly
to `tools.exec.safeBinTrustedDirs` .
2026-01-21 21:44:28 +00:00
Shell chaining and redirections are not auto-allowed in allowlist mode.
2026-01-23 00:10:19 +00:00
Shell chaining (`&&` , `||` , `;` ) is allowed when every top-level segment satisfies the allowlist
(including safe bins or skill auto-allow). Redirections remain unsupported in allowlist mode.
2026-02-02 16:53:09 -08:00
Command substitution (`$()` / backticks) is rejected during allowlist parsing, including inside
double quotes; use single quotes if you need literal `$()` text.
2026-02-21 19:16:15 +01:00
On macOS companion-app approvals, raw shell text containing shell control or expansion syntax
(`&&` , `||` , `;` , `|` , `` ` ``, ` $`, ` < `, ` >`, ` (`, ` )`) is treated as an allowlist miss unless
the shell binary itself is allowlisted.
2026-02-22 12:46:55 +01:00
For shell wrappers (`bash|sh|zsh ... -c/-lc` ), request-scoped env overrides are reduced to a
small explicit allowlist (`TERM` , `LANG` , `LC_*` , `COLORTERM` , `NO_COLOR` , `FORCE_COLOR` ).
2026-02-22 22:54:21 +01:00
For allow-always decisions in allowlist mode, known dispatch wrappers
(`env` , `nice` , `nohup` , `stdbuf` , `timeout` ) persist inner executable paths instead of wrapper
2026-02-24 03:06:34 +00:00
paths. Shell multiplexers (`busybox` , `toybox` ) are also unwrapped for shell applets (`sh` , `ash` ,
etc.) so inner executables are persisted instead of multiplexer binaries. If a wrapper or
multiplexer cannot be safely unwrapped, no allowlist entry is persisted automatically.
2026-01-23 00:10:19 +00:00
2026-02-19 14:07:43 +01:00
Default safe bins: `jq` , `cut` , `uniq` , `head` , `tail` , `tr` , `wc` .
`grep` and `sort` are not in the default list. If you opt in, keep explicit allowlist entries for
their non-stdin workflows.
2026-02-21 11:18:19 +01:00
For `grep` in safe-bin mode, provide the pattern with `-e` /`--regexp` ; positional pattern form is
rejected so file operands cannot be smuggled as ambiguous positionals.
2026-01-21 21:44:28 +00:00
2026-02-22 12:57:53 +01:00
### Safe bins versus allowlist
| Topic | `tools.exec.safeBins` | Allowlist (`exec-approvals.json` ) |
| ---------------- | ------------------------------------------------------ | ------------------------------------------------------------ |
| Goal | Auto-allow narrow stdin filters | Explicitly trust specific executables |
| Match type | Executable name + safe-bin argv policy | Resolved executable path glob pattern |
| Argument scope | Restricted by safe-bin profile and literal-token rules | Path match only; arguments are otherwise your responsibility |
| Typical examples | `jq` , `head` , `tail` , `wc` | `python3` , `node` , `ffmpeg` , custom CLIs |
| Best use | Low-risk text transforms in pipelines | Any tool with broader behavior or side effects |
Configuration location:
- `safeBins` comes from config (`tools.exec.safeBins` or per-agent `agents.list[].tools.exec.safeBins` ).
2026-02-22 22:42:29 +01:00
- `safeBinTrustedDirs` comes from config (`tools.exec.safeBinTrustedDirs` or per-agent `agents.list[].tools.exec.safeBinTrustedDirs` ).
2026-02-22 12:57:53 +01:00
- `safeBinProfiles` comes from config (`tools.exec.safeBinProfiles` or per-agent `agents.list[].tools.exec.safeBinProfiles` ). Per-agent profile keys override global keys.
- allowlist entries live in host-local `~/.openclaw/exec-approvals.json` under `agents.<id>.allowlist` (or via Control UI / `openclaw approvals allowlist ...` ).
2026-02-22 13:18:17 +01:00
- `openclaw security audit` warns with `tools.exec.safe_bins_interpreter_unprofiled` when interpreter/runtime bins appear in `safeBins` without explicit profiles.
- `openclaw doctor --fix` can scaffold missing custom `safeBinProfiles.<bin>` entries as `{}` (review and tighten afterward). Interpreter/runtime bins are not auto-scaffolded.
2026-02-22 12:57:53 +01:00
Custom profile example:
```json5
{
tools: {
exec: {
safeBins: ["jq", "myfilter"],
safeBinProfiles: {
myfilter: {
minPositional: 0,
maxPositional: 0,
allowedValueFlags: ["-n", "--limit"],
deniedFlags: ["-f", "--file", "-c", "--command"],
},
},
},
},
}
```
2026-01-18 08:54:34 +00:00
## Control UI editing
Use the **Control UI → Nodes → Exec approvals** card to edit defaults, per‑ agent
overrides, and allowlists. Pick a scope (Defaults or an agent), tweak the policy,
add/remove allowlist patterns, then **Save** . The UI shows **last used** metadata
per pattern so you can keep the list tidy.
2026-01-18 15:23:36 +00:00
The target selector chooses **Gateway** (local approvals) or a **Node** . Nodes
must advertise `system.execApprovals.get/set` (macOS app or headless node host).
If a node does not advertise exec approvals yet, edit its local
2026-01-30 03:15:10 +01:00
`~/.openclaw/exec-approvals.json` directly.
2026-01-18 15:23:36 +00:00
2026-01-30 03:15:10 +01:00
CLI: `openclaw approvals` supports gateway or node editing (see [Approvals CLI ](/cli/approvals )).
2026-01-18 08:54:34 +00:00
2026-01-18 01:33:52 +00:00
## Approval flow
2026-01-20 12:20:20 +00:00
When a prompt is required, the gateway broadcasts `exec.approval.requested` to operator clients.
The Control UI and macOS app resolve it via `exec.approval.resolve` , then the gateway forwards the
approved request to the node host.
2026-03-02 01:12:47 +00:00
For `host=node` , approval requests include a canonical `systemRunPlan` payload. The gateway uses
that plan as the authoritative command/cwd/session context when forwarding approved `system.run`
requests.
2026-01-22 00:49:02 +00:00
When approvals are required, the exec tool returns immediately with an approval id. Use that id to
correlate later system events (`Exec finished` / `Exec denied` ). If no decision arrives before the
timeout, the request is treated as an approval timeout and surfaced as a denial reason.
2026-01-20 12:20:20 +00:00
The confirmation dialog includes:
2026-01-31 21:13:13 +09:00
2026-01-18 01:33:52 +00:00
- command + args
- cwd
2026-01-18 04:27:33 +00:00
- agent id
- resolved executable path
- host + policy metadata
2026-01-18 01:33:52 +00:00
Actions:
2026-01-31 21:13:13 +09:00
2026-01-18 01:33:52 +00:00
- **Allow once** → run now
2026-01-18 04:27:33 +00:00
- **Always allow** → add to allowlist + run
2026-01-18 01:33:52 +00:00
- **Deny** → block
2026-01-24 12:56:40 -08:00
## Approval forwarding to chat channels
You can forward exec approval prompts to any chat channel (including plugin channels) and approve
them with `/approve` . This uses the normal outbound delivery pipeline.
Config:
2026-01-31 21:13:13 +09:00
2026-01-24 12:56:40 -08:00
```json5
{
approvals: {
exec: {
enabled: true,
mode: "session", // "session" | "targets" | "both"
agentFilter: ["main"],
sessionFilter: ["discord"], // substring or regex
targets: [
{ channel: "slack", to: "U12345678" },
2026-01-31 21:13:13 +09:00
{ channel: "telegram", to: "123456789" },
],
},
},
2026-01-24 12:56:40 -08:00
}
```
Reply in chat:
2026-01-31 21:13:13 +09:00
2026-01-24 12:56:40 -08:00
```
/approve < id > allow-once
/approve < id > allow-always
/approve < id > deny
```
2026-01-22 23:07:58 +00:00
### macOS IPC flow
2026-01-31 21:13:13 +09:00
2026-01-18 16:20:48 +00:00
```
2026-01-22 23:07:58 +00:00
Gateway -> Node Service (WS)
| IPC (UDS + token + HMAC + TTL)
v
Mac App (UI + approvals + system.run)
2026-01-18 16:20:48 +00:00
```
Security notes:
2026-01-31 21:13:13 +09:00
2026-01-18 16:20:48 +00:00
- Unix socket mode `0600` , token stored in `exec-approvals.json` .
- Same-UID peer check.
- Challenge/response (nonce + HMAC token + request hash) + short TTL.
2026-01-18 01:33:52 +00:00
## System events
2026-01-18 04:27:33 +00:00
Exec lifecycle is surfaced as system messages:
2026-01-31 21:13:13 +09:00
2026-01-22 00:49:02 +00:00
- `Exec running` (only if the command exceeds the running notice threshold)
- `Exec finished`
- `Exec denied`
2026-01-18 01:33:52 +00:00
2026-01-18 04:27:33 +00:00
These are posted to the agent’ s session after the node reports the event.
2026-01-22 00:49:02 +00:00
Gateway-host exec approvals emit the same lifecycle events when the command finishes (and optionally when running longer than the threshold).
Approval-gated execs reuse the approval id as the `runId` in these messages for easy correlation.
2026-01-18 01:33:52 +00:00
## Implications
2026-01-18 04:27:33 +00:00
- **full** is powerful; prefer allowlists when possible.
- **ask** keeps you in the loop while still allowing fast approvals.
- Per-agent allowlists prevent one agent’ s approvals from leaking into others.
2026-01-26 22:18:36 +00:00
- Approvals only apply to host exec requests from **authorized senders** . Unauthorized senders cannot issue `/exec` .
- `/exec security=full` is a session-level convenience for authorized operators and skips approvals by design.
To hard-block host exec, set approvals security to `deny` or deny the `exec` tool via tool policy.
2026-01-18 01:33:52 +00:00
Related:
2026-01-31 21:13:13 +09:00
2026-01-18 01:33:52 +00:00
- [Exec tool ](/tools/exec )
- [Elevated mode ](/tools/elevated )
- [Skills ](/tools/skills )