diff --git a/.openclaw-sync/source.json b/.openclaw-sync/source.json
index aed61d9cc..3416fa57e 100644
--- a/.openclaw-sync/source.json
+++ b/.openclaw-sync/source.json
@@ -1,5 +1,5 @@
{
"repository": "openclaw/openclaw",
- "sha": "fb47c1d6bf0b95c788f647a05faec934d08abb19",
- "syncedAt": "2026-04-23T17:04:12.975Z"
+ "sha": "4792a50710aab6d0f30d7478967ef185892baf43",
+ "syncedAt": "2026-04-23T17:10:15.586Z"
}
diff --git a/docs/gateway/protocol.md b/docs/gateway/protocol.md
index 393aa7b8d..991537243 100644
--- a/docs/gateway/protocol.md
+++ b/docs/gateway/protocol.md
@@ -4,7 +4,7 @@ read_when:
- Implementing or updating gateway WS clients
- Debugging protocol mismatches or connect failures
- Regenerating protocol schema/models
-title: "Gateway Protocol"
+title: "Gateway protocol"
---
# Gateway protocol (WebSocket)
@@ -260,199 +260,129 @@ Each client connection keeps its own per-client sequence number so broadcasts pr
## Common RPC method families
-This page is not a generated full dump, but the public WS surface is broader
-than the handshake/auth examples above. These are the main method families the
-Gateway exposes today.
+The public WS surface is broader than the handshake/auth examples above. This
+is not a generated dump — `hello-ok.features.methods` is a conservative
+discovery list built from `src/gateway/server-methods-list.ts` plus loaded
+plugin/channel method exports. Treat it as feature discovery, not a full
+enumeration of `src/gateway/server-methods/*.ts`.
-`hello-ok.features.methods` is a conservative discovery list built from
-`src/gateway/server-methods-list.ts` plus loaded plugin/channel method exports.
-Treat it as feature discovery, not as a generated dump of every callable helper
-implemented in `src/gateway/server-methods/*.ts`.
+
+
+ - `health` returns the cached or freshly probed gateway health snapshot.
+ - `diagnostics.stability` returns the recent bounded diagnostic stability recorder. It keeps operational metadata such as event names, counts, byte sizes, memory readings, queue/session state, channel/plugin names, and session ids. It does not keep chat text, webhook bodies, tool outputs, raw request or response bodies, tokens, cookies, or secret values. Operator read scope is required.
+ - `status` returns the `/status`-style gateway summary; sensitive fields are included only for admin-scoped operator clients.
+ - `gateway.identity.get` returns the gateway device identity used by relay and pairing flows.
+ - `system-presence` returns the current presence snapshot for connected operator/node devices.
+ - `system-event` appends a system event and can update/broadcast presence context.
+ - `last-heartbeat` returns the latest persisted heartbeat event.
+ - `set-heartbeats` toggles heartbeat processing on the gateway.
+
-### System and identity
+
+ - `models.list` returns the runtime-allowed model catalog.
+ - `usage.status` returns provider usage windows/remaining quota summaries.
+ - `usage.cost` returns aggregated cost usage summaries for a date range.
+ - `doctor.memory.status` returns vector-memory / embedding readiness for the active default agent workspace.
+ - `sessions.usage` returns per-session usage summaries.
+ - `sessions.usage.timeseries` returns timeseries usage for one session.
+ - `sessions.usage.logs` returns usage log entries for one session.
+
-- `health` returns the cached or freshly probed gateway health snapshot.
-- `diagnostics.stability` returns the recent bounded diagnostic stability
- recorder. It keeps operational metadata such as event names, counts, byte
- sizes, memory readings, queue/session state, channel/plugin names, and session
- ids. It does not keep chat text, webhook bodies, tool outputs, raw request or
- response bodies, tokens, cookies, or secret values. Operator read scope is
- required.
-- `status` returns the `/status`-style gateway summary; sensitive fields are
- included only for admin-scoped operator clients.
-- `gateway.identity.get` returns the gateway device identity used by relay and
- pairing flows.
-- `system-presence` returns the current presence snapshot for connected
- operator/node devices.
-- `system-event` appends a system event and can update/broadcast presence
- context.
-- `last-heartbeat` returns the latest persisted heartbeat event.
-- `set-heartbeats` toggles heartbeat processing on the gateway.
+
+ - `channels.status` returns built-in + bundled channel/plugin status summaries.
+ - `channels.logout` logs out a specific channel/account where the channel supports logout.
+ - `web.login.start` starts a QR/web login flow for the current QR-capable web channel provider.
+ - `web.login.wait` waits for that QR/web login flow to complete and starts the channel on success.
+ - `push.test` sends a test APNs push to a registered iOS node.
+ - `voicewake.get` returns the stored wake-word triggers.
+ - `voicewake.set` updates wake-word triggers and broadcasts the change.
+
-### Models and usage
+
+ - `send` is the direct outbound-delivery RPC for channel/account/thread-targeted sends outside the chat runner.
+ - `logs.tail` returns the configured gateway file-log tail with cursor/limit and max-byte controls.
+
-- `models.list` returns the runtime-allowed model catalog.
-- `usage.status` returns provider usage windows/remaining quota summaries.
-- `usage.cost` returns aggregated cost usage summaries for a date range.
-- `doctor.memory.status` returns vector-memory / embedding readiness for the
- active default agent workspace.
-- `sessions.usage` returns per-session usage summaries.
-- `sessions.usage.timeseries` returns timeseries usage for one session.
-- `sessions.usage.logs` returns usage log entries for one session.
+
+ - `talk.config` returns the effective Talk config payload; `includeSecrets` requires `operator.talk.secrets` (or `operator.admin`).
+ - `talk.mode` sets/broadcasts the current Talk mode state for WebChat/Control UI clients.
+ - `talk.speak` synthesizes speech through the active Talk speech provider.
+ - `tts.status` returns TTS enabled state, active provider, fallback providers, and provider config state.
+ - `tts.providers` returns the visible TTS provider inventory.
+ - `tts.enable` and `tts.disable` toggle TTS prefs state.
+ - `tts.setProvider` updates the preferred TTS provider.
+ - `tts.convert` runs one-shot text-to-speech conversion.
+
-### Channels and login helpers
+
+ - `secrets.reload` re-resolves active SecretRefs and swaps runtime secret state only on full success.
+ - `secrets.resolve` resolves command-target secret assignments for a specific command/target set.
+ - `config.get` returns the current config snapshot and hash.
+ - `config.set` writes a validated config payload.
+ - `config.patch` merges a partial config update.
+ - `config.apply` validates + replaces the full config payload.
+ - `config.schema` returns the live config schema payload used by Control UI and CLI tooling: schema, `uiHints`, version, and generation metadata, including plugin + channel schema metadata when the runtime can load it. The schema includes field `title` / `description` metadata derived from the same labels and help text used by the UI, including nested object, wildcard, array-item, and `anyOf` / `oneOf` / `allOf` composition branches when matching field documentation exists.
+ - `config.schema.lookup` returns a path-scoped lookup payload for one config path: normalized path, a shallow schema node, matched hint + `hintPath`, and immediate child summaries for UI/CLI drill-down. Lookup schema nodes keep the user-facing docs and common validation fields (`title`, `description`, `type`, `enum`, `const`, `format`, `pattern`, numeric/string/array/object bounds, and flags like `additionalProperties`, `deprecated`, `readOnly`, `writeOnly`). Child summaries expose `key`, normalized `path`, `type`, `required`, `hasChildren`, plus the matched `hint` / `hintPath`.
+ - `update.run` runs the gateway update flow and schedules a restart only when the update itself succeeded.
+ - `wizard.start`, `wizard.next`, `wizard.status`, and `wizard.cancel` expose the onboarding wizard over WS RPC.
+
-- `channels.status` returns built-in + bundled channel/plugin status summaries.
-- `channels.logout` logs out a specific channel/account where the channel
- supports logout.
-- `web.login.start` starts a QR/web login flow for the current QR-capable web
- channel provider.
-- `web.login.wait` waits for that QR/web login flow to complete and starts the
- channel on success.
-- `push.test` sends a test APNs push to a registered iOS node.
-- `voicewake.get` returns the stored wake-word triggers.
-- `voicewake.set` updates wake-word triggers and broadcasts the change.
+
+ - `agents.list` returns configured agent entries.
+ - `agents.create`, `agents.update`, and `agents.delete` manage agent records and workspace wiring.
+ - `agents.files.list`, `agents.files.get`, and `agents.files.set` manage the bootstrap workspace files exposed for an agent.
+ - `agent.identity.get` returns the effective assistant identity for an agent or session.
+ - `agent.wait` waits for a run to finish and returns the terminal snapshot when available.
+
-### Messaging and logs
+
+ - `sessions.list` returns the current session index.
+ - `sessions.subscribe` and `sessions.unsubscribe` toggle session change event subscriptions for the current WS client.
+ - `sessions.messages.subscribe` and `sessions.messages.unsubscribe` toggle transcript/message event subscriptions for one session.
+ - `sessions.preview` returns bounded transcript previews for specific session keys.
+ - `sessions.resolve` resolves or canonicalizes a session target.
+ - `sessions.create` creates a new session entry.
+ - `sessions.send` sends a message into an existing session.
+ - `sessions.steer` is the interrupt-and-steer variant for an active session.
+ - `sessions.abort` aborts active work for a session.
+ - `sessions.patch` updates session metadata/overrides.
+ - `sessions.reset`, `sessions.delete`, and `sessions.compact` perform session maintenance.
+ - `sessions.get` returns the full stored session row.
+ - Chat execution still uses `chat.history`, `chat.send`, `chat.abort`, and `chat.inject`. `chat.history` is display-normalized for UI clients: inline directive tags are stripped from visible text, plain-text tool-call XML payloads (including `...`, `...`, `...`, `...`, and truncated tool-call blocks) and leaked ASCII/full-width model control tokens are stripped, pure silent-token assistant rows such as exact `NO_REPLY` / `no_reply` are omitted, and oversized rows can be replaced with placeholders.
+
-- `send` is the direct outbound-delivery RPC for channel/account/thread-targeted
- sends outside the chat runner.
-- `logs.tail` returns the configured gateway file-log tail with cursor/limit and
- max-byte controls.
+
+ - `device.pair.list` returns pending and approved paired devices.
+ - `device.pair.approve`, `device.pair.reject`, and `device.pair.remove` manage device-pairing records.
+ - `device.token.rotate` rotates a paired device token within its approved role and scope bounds.
+ - `device.token.revoke` revokes a paired device token.
+
-### Talk and TTS
+
+ - `node.pair.request`, `node.pair.list`, `node.pair.approve`, `node.pair.reject`, and `node.pair.verify` cover node pairing and bootstrap verification.
+ - `node.list` and `node.describe` return known/connected node state.
+ - `node.rename` updates a paired node label.
+ - `node.invoke` forwards a command to a connected node.
+ - `node.invoke.result` returns the result for an invoke request.
+ - `node.event` carries node-originated events back into the gateway.
+ - `node.canvas.capability.refresh` refreshes scoped canvas-capability tokens.
+ - `node.pending.pull` and `node.pending.ack` are the connected-node queue APIs.
+ - `node.pending.enqueue` and `node.pending.drain` manage durable pending work for offline/disconnected nodes.
+
-- `talk.config` returns the effective Talk config payload; `includeSecrets`
- requires `operator.talk.secrets` (or `operator.admin`).
-- `talk.mode` sets/broadcasts the current Talk mode state for WebChat/Control UI
- clients.
-- `talk.speak` synthesizes speech through the active Talk speech provider.
-- `tts.status` returns TTS enabled state, active provider, fallback providers,
- and provider config state.
-- `tts.providers` returns the visible TTS provider inventory.
-- `tts.enable` and `tts.disable` toggle TTS prefs state.
-- `tts.setProvider` updates the preferred TTS provider.
-- `tts.convert` runs one-shot text-to-speech conversion.
+
+ - `exec.approval.request`, `exec.approval.get`, `exec.approval.list`, and `exec.approval.resolve` cover one-shot exec approval requests plus pending approval lookup/replay.
+ - `exec.approval.waitDecision` waits on one pending exec approval and returns the final decision (or `null` on timeout).
+ - `exec.approvals.get` and `exec.approvals.set` manage gateway exec approval policy snapshots.
+ - `exec.approvals.node.get` and `exec.approvals.node.set` manage node-local exec approval policy via node relay commands.
+ - `plugin.approval.request`, `plugin.approval.list`, `plugin.approval.waitDecision`, and `plugin.approval.resolve` cover plugin-defined approval flows.
+
-### Secrets, config, update, and wizard
-
-- `secrets.reload` re-resolves active SecretRefs and swaps runtime secret state
- only on full success.
-- `secrets.resolve` resolves command-target secret assignments for a specific
- command/target set.
-- `config.get` returns the current config snapshot and hash.
-- `config.set` writes a validated config payload.
-- `config.patch` merges a partial config update.
-- `config.apply` validates + replaces the full config payload.
-- `config.schema` returns the live config schema payload used by Control UI and
- CLI tooling: schema, `uiHints`, version, and generation metadata, including
- plugin + channel schema metadata when the runtime can load it. The schema
- includes field `title` / `description` metadata derived from the same labels
- and help text used by the UI, including nested object, wildcard, array-item,
- and `anyOf` / `oneOf` / `allOf` composition branches when matching field
- documentation exists.
-- `config.schema.lookup` returns a path-scoped lookup payload for one config
- path: normalized path, a shallow schema node, matched hint + `hintPath`, and
- immediate child summaries for UI/CLI drill-down.
- - Lookup schema nodes keep the user-facing docs and common validation fields:
- `title`, `description`, `type`, `enum`, `const`, `format`, `pattern`,
- numeric/string/array/object bounds, and boolean flags like
- `additionalProperties`, `deprecated`, `readOnly`, `writeOnly`.
- - Child summaries expose `key`, normalized `path`, `type`, `required`,
- `hasChildren`, plus the matched `hint` / `hintPath`.
-- `update.run` runs the gateway update flow and schedules a restart only when
- the update itself succeeded.
-- `wizard.start`, `wizard.next`, `wizard.status`, and `wizard.cancel` expose the
- onboarding wizard over WS RPC.
-
-### Existing major families
-
-#### Agent and workspace helpers
-
-- `agents.list` returns configured agent entries.
-- `agents.create`, `agents.update`, and `agents.delete` manage agent records and
- workspace wiring.
-- `agents.files.list`, `agents.files.get`, and `agents.files.set` manage the
- bootstrap workspace files exposed for an agent.
-- `agent.identity.get` returns the effective assistant identity for an agent or
- session.
-- `agent.wait` waits for a run to finish and returns the terminal snapshot when
- available.
-
-#### Session control
-
-- `sessions.list` returns the current session index.
-- `sessions.subscribe` and `sessions.unsubscribe` toggle session change event
- subscriptions for the current WS client.
-- `sessions.messages.subscribe` and `sessions.messages.unsubscribe` toggle
- transcript/message event subscriptions for one session.
-- `sessions.preview` returns bounded transcript previews for specific session
- keys.
-- `sessions.resolve` resolves or canonicalizes a session target.
-- `sessions.create` creates a new session entry.
-- `sessions.send` sends a message into an existing session.
-- `sessions.steer` is the interrupt-and-steer variant for an active session.
-- `sessions.abort` aborts active work for a session.
-- `sessions.patch` updates session metadata/overrides.
-- `sessions.reset`, `sessions.delete`, and `sessions.compact` perform session
- maintenance.
-- `sessions.get` returns the full stored session row.
-- chat execution still uses `chat.history`, `chat.send`, `chat.abort`, and
- `chat.inject`.
-- `chat.history` is display-normalized for UI clients: inline directive tags are
- stripped from visible text, plain-text tool-call XML payloads (including
- `...`, `...`,
- `...`, `...`, and
- truncated tool-call blocks) and leaked ASCII/full-width model control tokens
- are stripped, pure silent-token assistant rows such as exact `NO_REPLY` /
- `no_reply` are omitted, and oversized rows can be replaced with placeholders.
-
-#### Device pairing and device tokens
-
-- `device.pair.list` returns pending and approved paired devices.
-- `device.pair.approve`, `device.pair.reject`, and `device.pair.remove` manage
- device-pairing records.
-- `device.token.rotate` rotates a paired device token within its approved role
- and scope bounds.
-- `device.token.revoke` revokes a paired device token.
-
-#### Node pairing, invoke, and pending work
-
-- `node.pair.request`, `node.pair.list`, `node.pair.approve`,
- `node.pair.reject`, and `node.pair.verify` cover node pairing and bootstrap
- verification.
-- `node.list` and `node.describe` return known/connected node state.
-- `node.rename` updates a paired node label.
-- `node.invoke` forwards a command to a connected node.
-- `node.invoke.result` returns the result for an invoke request.
-- `node.event` carries node-originated events back into the gateway.
-- `node.canvas.capability.refresh` refreshes scoped canvas-capability tokens.
-- `node.pending.pull` and `node.pending.ack` are the connected-node queue APIs.
-- `node.pending.enqueue` and `node.pending.drain` manage durable pending work
- for offline/disconnected nodes.
-
-#### Approval families
-
-- `exec.approval.request`, `exec.approval.get`, `exec.approval.list`, and
- `exec.approval.resolve` cover one-shot exec approval requests plus pending
- approval lookup/replay.
-- `exec.approval.waitDecision` waits on one pending exec approval and returns
- the final decision (or `null` on timeout).
-- `exec.approvals.get` and `exec.approvals.set` manage gateway exec approval
- policy snapshots.
-- `exec.approvals.node.get` and `exec.approvals.node.set` manage node-local exec
- approval policy via node relay commands.
-- `plugin.approval.request`, `plugin.approval.list`,
- `plugin.approval.waitDecision`, and `plugin.approval.resolve` cover
- plugin-defined approval flows.
-
-#### Other major families
-
-- automation:
- - `wake` schedules an immediate or next-heartbeat wake text injection
- - `cron.list`, `cron.status`, `cron.add`, `cron.update`, `cron.remove`,
- `cron.run`, `cron.runs`
-- skills/tools: `commands.list`, `skills.*`, `tools.catalog`, `tools.effective`
+
+ - Automation: `wake` schedules an immediate or next-heartbeat wake text injection; `cron.list`, `cron.status`, `cron.add`, `cron.update`, `cron.remove`, `cron.run`, `cron.runs` manage scheduled work.
+ - Skills and tools: `commands.list`, `skills.*`, `tools.catalog`, `tools.effective`.
+
+
### Common event families
diff --git a/docs/tools/exec-approvals.md b/docs/tools/exec-approvals.md
index 23d8b86a4..fdacdc594 100644
--- a/docs/tools/exec-approvals.md
+++ b/docs/tools/exec-approvals.md
@@ -4,35 +4,44 @@ read_when:
- Configuring exec approvals or allowlists
- Implementing exec approval UX in the macOS app
- Reviewing sandbox escape prompts and implications
-title: "Exec Approvals"
+title: "Exec approvals"
---
# Exec approvals
-Exec approvals are the **companion app / node host guardrail** for letting a sandboxed agent run
-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.
-Exec approvals are **in addition** to tool policy and elevated gating (unless elevated is set to `full`, which skips approvals).
-Effective policy is the **stricter** of `tools.exec.*` and approvals defaults; if an approvals field is omitted, the `tools.exec` value is used.
-Host exec also uses the local approvals state on that machine. A host-local
-`ask: "always"` in `~/.openclaw/exec-approvals.json` keeps prompting even if
-session or config defaults request `ask: "on-miss"`.
-Use `openclaw approvals get`, `openclaw approvals get --gateway`, or
-`openclaw approvals get --node ` to inspect the requested policy,
-host policy sources, and the effective result.
-For the local machine, `openclaw exec-policy show` exposes the same merged view and
-`openclaw exec-policy set|preset` can synchronize the local requested policy with the
-local host approvals file in one step. When a local scope requests `host=node`,
-`openclaw exec-policy show` reports that scope as node-managed at runtime instead of
-pretending the local approvals file is the effective source of truth.
+Exec approvals are the **companion app / node host guardrail** for letting a
+sandboxed agent run commands on a real host (`gateway` or `node`). A safety
+interlock: commands are allowed only when policy + allowlist + (optional) user
+approval all agree. Exec approvals stack **on top of** tool policy and elevated
+gating (unless elevated is set to `full`, which skips approvals).
-If the companion app UI is **not available**, any request that requires a prompt is
-resolved by the **ask fallback** (default: deny).
+
+Effective policy is the **stricter** of `tools.exec.*` and approvals defaults;
+if an approvals field is omitted, the `tools.exec` value is used. Host exec
+also uses local approvals state on that machine — a host-local `ask: "always"`
+in `~/.openclaw/exec-approvals.json` keeps prompting even if session or config
+defaults request `ask: "on-miss"`.
+
-Native chat approval clients can also expose channel-specific affordances on the
-pending approval message. For example, Matrix can seed reaction shortcuts on the
-approval prompt (`✅` allow once, `❌` deny, and `♾️` allow always when available)
-while still leaving the `/approve ...` commands in the message as a fallback.
+## Inspecting the effective policy
+
+- `openclaw approvals get`, `... --gateway`, `... --node ` — show requested policy, host policy sources, and the effective result.
+- `openclaw exec-policy show` — local-machine merged view.
+- `openclaw exec-policy set|preset` — synchronize the local requested policy with the local host approvals file in one step.
+
+When a local scope requests `host=node`, `exec-policy show` reports that scope
+as node-managed at runtime instead of pretending the local approvals file is
+the source of truth.
+
+If the companion app UI is **not available**, any request that would normally
+prompt is resolved by the **ask fallback** (default: deny).
+
+
+Native chat approval clients can seed channel-specific affordances on the
+pending approval message. For example, Matrix seeds reaction shortcuts (`✅`
+allow once, `❌` deny, `♾️` allow always) while still leaving `/approve ...`
+commands in the message as a fallback.
+
## Where it applies
@@ -268,60 +277,19 @@ Important trust notes:
## Safe bins (stdin-only)
-`tools.exec.safeBins` defines a small list of **stdin-only** binaries (for example `cut`)
-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.
-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.`.
-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`, `sort --compress-program`, `sort --random-source`,
-`sort --temporary-directory`/`-T`, `wc --files0-from`, `jq -f/--from-file`,
-`grep -f/--file`).
-Safe bins also enforce explicit per-binary flag policy for options that break stdin-only
-behavior (for example `sort -o/--output/--compress-program` and grep recursive flags).
-Long options are validated fail-closed in safe-bin mode: unknown flags and ambiguous
-abbreviations are rejected.
-Denied flags by safe-bin profile:
+`tools.exec.safeBins` defines a small list of **stdin-only** binaries (for
+example `cut`) 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. Treat this as a narrow fast-path for
+stream filters, not a general trust list.
-[//]: # "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`
-- `sort`: `--compress-program`, `--files0-from`, `--output`, `--random-source`, `--temporary-directory`, `-T`, `-o`
-- `wc`: `--files0-from`
-
-[//]: # "SAFE_BIN_DENIED_FLAGS:END"
-
-Safe bins also force argv tokens to be treated as **literal text** at execution time (no globbing
-and no `$VARS` expansion) for stdin-only segments, so patterns like `*` or `$HOME/...` cannot be
-used to smuggle file reads.
-Safe bins must also resolve from trusted binary directories (system defaults plus optional
-`tools.exec.safeBinTrustedDirs`). `PATH` entries are never auto-trusted.
-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`.
-Shell chaining and redirections are not auto-allowed in allowlist mode.
-
-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.
-Command substitution (`$()` / backticks) is rejected during allowlist parsing, including inside
-double quotes; use single quotes if you need literal `$()` text.
-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.
-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`).
-For allow-always decisions in allowlist mode, known dispatch wrappers
-(`env`, `nice`, `nohup`, `stdbuf`, `timeout`) persist inner executable paths instead of wrapper
-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.
-If you allowlist interpreters like `python3` or `node`, prefer `tools.exec.strictInlineEval=true` so inline eval still requires an explicit approval. In strict mode, `allow-always` can still persist benign interpreter/script invocations, but inline-eval carriers are not persisted automatically.
+
+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.`.
+
Default safe bins:
@@ -331,10 +299,78 @@ Default safe bins:
[//]: # "SAFE_BIN_DEFAULTS:END"
-`grep` and `sort` are not in the default list. If you opt in, keep explicit allowlist entries for
-their non-stdin workflows.
-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.
+`grep` and `sort` are not in the default list. If you opt in, keep explicit
+allowlist entries for their non-stdin workflows. 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.
+
+
+
+ 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; long options are validated fail-closed (unknown flags and ambiguous
+ abbreviations are rejected).
+
+ 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`
+ - `sort`: `--compress-program`, `--files0-from`, `--output`, `--random-source`, `--temporary-directory`, `-T`, `-o`
+ - `wc`: `--files0-from`
+
+ [//]: # "SAFE_BIN_DENIED_FLAGS:END"
+
+ Safe bins also force argv tokens to be treated as **literal text** at
+ execution time (no globbing and no `$VARS` expansion) for stdin-only
+ segments, so patterns like `*` or `$HOME/...` cannot be used to smuggle
+ file reads.
+
+
+
+
+ Safe bins must resolve from trusted binary directories (system defaults
+ plus optional `tools.exec.safeBinTrustedDirs`). `PATH` entries are never
+ auto-trusted. Default trusted 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`.
+
+
+
+ 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. Command substitution
+ (`$()` / backticks) is rejected during allowlist parsing, including inside
+ double quotes; use single quotes if you need literal `$()` text.
+
+ 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.
+
+ 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`).
+
+ For `allow-always` decisions in allowlist mode, known dispatch wrappers
+ (`env`, `nice`, `nohup`, `stdbuf`, `timeout`) persist the inner executable
+ path instead of the wrapper path. Shell multiplexers (`busybox`, `toybox`)
+ are unwrapped for shell applets (`sh`, `ash`, etc.) the same way. If a
+ wrapper or multiplexer cannot be safely unwrapped, no allowlist entry is
+ persisted automatically.
+
+ If you allowlist interpreters like `python3` or `node`, prefer
+ `tools.exec.strictInlineEval=true` so inline eval still requires an
+ explicit approval. In strict mode, `allow-always` can still persist benign
+ interpreter/script invocations, but inline-eval carriers are not persisted
+ automatically.
+
+
+
### Safe bins versus allowlist
@@ -642,20 +678,29 @@ stale results from a prior successful run.
- **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.
+- Per-agent allowlists prevent one agent's approvals from leaking into others.
- 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.
-
-Related:
-
-- [Exec tool](/tools/exec)
-- [Elevated mode](/tools/elevated)
-- [Skills](/tools/skills)
+- `/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.
## Related
-- [Exec](/tools/exec) — shell command execution tool
-- [Sandboxing](/gateway/sandboxing) — sandbox modes and workspace access
-- [Security](/gateway/security) — security model and hardening
-- [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated) — when to use each
+
+
+ Shell command execution tool.
+
+
+ Break-glass path that also skips approvals.
+
+
+ Sandbox modes and workspace access.
+
+
+ Security model and hardening.
+
+
+ When to reach for each control.
+
+
+ Skill-backed auto-allow behavior.
+
+