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-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-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-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`,
|
|
|
|
|
|
`sort --files0-from`, `wc --files0-from`, `jq -f/--from-file`, `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
|
|
|
|
|
|
behavior (for example `sort -o/--output` and grep recursive flags).
|
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-18 04:54:46 +01:00
|
|
|
|
Safe bins must also resolve from trusted binary directories (system defaults plus the gateway
|
|
|
|
|
|
process `PATH` at startup). This blocks request-scoped PATH hijacking attempts.
|
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-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-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-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)
|