






















@@ -0,0 +1,342 @@
1+---
2+summary: "Advanced exec approvals: safe bins, interpreter binding, approval forwarding, native delivery"
3+read_when:
4+ - Configuring safe bins or custom safe-bin profiles
5+ - Forwarding approvals to Slack/Discord/Telegram or other chat channels
6+ - Implementing a native approval client for a channel
7+title: "Exec approvals — advanced"
8+---
9+10+Advanced exec-approval topics: the `safeBins` fast-path, interpreter/runtime
11+binding, and approval-forwarding to chat channels (including native delivery).
12+For the core policy and approval flow, see [Exec approvals](/tools/exec-approvals).
13+14+## Safe bins (stdin-only)
15+16+`tools.exec.safeBins` defines a small list of **stdin-only** binaries (for
17+example `cut`) that can run in allowlist mode **without** explicit allowlist
18+entries. Safe bins reject positional file args and path-like tokens, so they
19+can only operate on the incoming stream. Treat this as a narrow fast-path for
20+stream filters, not a general trust list.
21+22+<Warning>
23+Do **not** add interpreter or runtime binaries (for example `python3`, `node`,
24+`ruby`, `bash`, `sh`, `zsh`) to `safeBins`. If a command can evaluate code,
25+execute subcommands, or read files by design, prefer explicit allowlist entries
26+and keep approval prompts enabled. Custom safe bins must define an explicit
27+profile in `tools.exec.safeBinProfiles.<bin>`.
28+</Warning>
29+30+Default safe bins:
31+32+[//]: # "SAFE_BIN_DEFAULTS:START"
33+34+`cut`, `uniq`, `head`, `tail`, `tr`, `wc`
35+36+[//]: # "SAFE_BIN_DEFAULTS:END"
37+38+`grep` and `sort` are not in the default list. If you opt in, keep explicit
39+allowlist entries for their non-stdin workflows. For `grep` in safe-bin mode,
40+provide the pattern with `-e`/`--regexp`; positional pattern form is rejected
41+so file operands cannot be smuggled as ambiguous positionals.
42+43+### Argv validation and denied flags
44+45+Validation is deterministic from argv shape only (no host filesystem existence
46+checks), which prevents file-existence oracle behavior from allow/deny
47+differences. File-oriented options are denied for default safe bins; long
48+options are validated fail-closed (unknown flags and ambiguous abbreviations are
49+rejected).
50+51+Denied flags by safe-bin profile:
52+53+[//]: # "SAFE_BIN_DENIED_FLAGS:START"
54+55+- `grep`: `--dereference-recursive`, `--directories`, `--exclude-from`, `--file`, `--recursive`, `-R`, `-d`, `-f`, `-r`
56+- `jq`: `--argfile`, `--from-file`, `--library-path`, `--rawfile`, `--slurpfile`, `-L`, `-f`
57+- `sort`: `--compress-program`, `--files0-from`, `--output`, `--random-source`, `--temporary-directory`, `-T`, `-o`
58+- `wc`: `--files0-from`
59+60+[//]: # "SAFE_BIN_DENIED_FLAGS:END"
61+62+Safe bins also force argv tokens to be treated as **literal text** at execution
63+time (no globbing and no `$VARS` expansion) for stdin-only segments, so patterns
64+like `*` or `$HOME/...` cannot be used to smuggle file reads.
65+66+### Trusted binary directories
67+68+Safe bins must resolve from trusted binary directories (system defaults plus
69+optional `tools.exec.safeBinTrustedDirs`). `PATH` entries are never auto-trusted.
70+Default trusted directories are intentionally minimal: `/bin`, `/usr/bin`. If
71+your safe-bin executable lives in package-manager/user paths (for example
72+`/opt/homebrew/bin`, `/usr/local/bin`, `/opt/local/bin`, `/snap/bin`), add them
73+explicitly to `tools.exec.safeBinTrustedDirs`.
74+75+### Shell chaining, wrappers, and multiplexers
76+77+Shell chaining (`&&`, `||`, `;`) is allowed when every top-level segment
78+satisfies the allowlist (including safe bins or skill auto-allow). Redirections
79+remain unsupported in allowlist mode. Command substitution (`$()` / backticks) is
80+rejected during allowlist parsing, including inside double quotes; use single
81+quotes if you need literal `$()` text.
82+83+On macOS companion-app approvals, raw shell text containing shell control or
84+expansion syntax (`&&`, `||`, `;`, `|`, `` ` ``, `$`, `<`, `>`, `(`, `)`) is
85+treated as an allowlist miss unless the shell binary itself is allowlisted.
86+87+For shell wrappers (`bash|sh|zsh ... -c/-lc`), request-scoped env overrides are
88+reduced to a small explicit allowlist (`TERM`, `LANG`, `LC_*`, `COLORTERM`,
89+`NO_COLOR`, `FORCE_COLOR`).
90+91+For `allow-always` decisions in allowlist mode, known dispatch wrappers (`env`,
92+`nice`, `nohup`, `stdbuf`, `timeout`) persist the inner executable path instead
93+of the wrapper path. Shell multiplexers (`busybox`, `toybox`) are unwrapped for
94+shell applets (`sh`, `ash`, etc.) the same way. If a wrapper or multiplexer
95+cannot be safely unwrapped, no allowlist entry is persisted automatically.
96+97+If you allowlist interpreters like `python3` or `node`, prefer
98+`tools.exec.strictInlineEval=true` so inline eval still requires an explicit
99+approval. In strict mode, `allow-always` can still persist benign
100+interpreter/script invocations, but inline-eval carriers are not persisted
101+automatically.
102+103+### Safe bins versus allowlist
104+105+| Topic | `tools.exec.safeBins` | Allowlist (`exec-approvals.json`) |
106+| ---------------- | ------------------------------------------------------ | ------------------------------------------------------------ |
107+| Goal | Auto-allow narrow stdin filters | Explicitly trust specific executables |
108+| Match type | Executable name + safe-bin argv policy | Resolved executable path glob pattern |
109+| Argument scope | Restricted by safe-bin profile and literal-token rules | Path match only; arguments are otherwise your responsibility |
110+| Typical examples | `head`, `tail`, `tr`, `wc` | `jq`, `python3`, `node`, `ffmpeg`, custom CLIs |
111+| Best use | Low-risk text transforms in pipelines | Any tool with broader behavior or side effects |
112+113+Configuration location:
114+115+- `safeBins` comes from config (`tools.exec.safeBins` or per-agent `agents.list[].tools.exec.safeBins`).
116+- `safeBinTrustedDirs` comes from config (`tools.exec.safeBinTrustedDirs` or per-agent `agents.list[].tools.exec.safeBinTrustedDirs`).
117+- `safeBinProfiles` comes from config (`tools.exec.safeBinProfiles` or per-agent `agents.list[].tools.exec.safeBinProfiles`). Per-agent profile keys override global keys.
118+- allowlist entries live in host-local `~/.openclaw/exec-approvals.json` under `agents.<id>.allowlist` (or via Control UI / `openclaw approvals allowlist ...`).
119+- `openclaw security audit` warns with `tools.exec.safe_bins_interpreter_unprofiled` when interpreter/runtime bins appear in `safeBins` without explicit profiles.
120+- `openclaw doctor --fix` can scaffold missing custom `safeBinProfiles.<bin>` entries as `{}` (review and tighten afterward). Interpreter/runtime bins are not auto-scaffolded.
121+122+Custom profile example:
123+124+```json5
125+{
126+ tools: {
127+ exec: {
128+ safeBins: ["jq", "myfilter"],
129+ safeBinProfiles: {
130+ myfilter: {
131+ minPositional: 0,
132+ maxPositional: 0,
133+ allowedValueFlags: ["-n", "--limit"],
134+ deniedFlags: ["-f", "--file", "-c", "--command"],
135+ },
136+ },
137+ },
138+ },
139+}
140+```
141+142+If you explicitly opt `jq` into `safeBins`, OpenClaw still rejects the `env` builtin in safe-bin
143+mode so `jq -n env` cannot dump the host process environment without an explicit allowlist path
144+or approval prompt.
145+146+## Interpreter/runtime commands
147+148+Approval-backed interpreter/runtime runs are intentionally conservative:
149+150+- Exact argv/cwd/env context is always bound.
151+- Direct shell script and direct runtime file forms are best-effort bound to one concrete local
152+ file snapshot.
153+- Common package-manager wrapper forms that still resolve to one direct local file (for example
154+`pnpm exec`, `pnpm node`, `npm exec`, `npx`) are unwrapped before binding.
155+- If OpenClaw cannot identify exactly one concrete local file for an interpreter/runtime command
156+ (for example package scripts, eval forms, runtime-specific loader chains, or ambiguous multi-file
157+ forms), approval-backed execution is denied instead of claiming semantic coverage it does not
158+ have.
159+- For those workflows, prefer sandboxing, a separate host boundary, or an explicit trusted
160+ allowlist/full workflow where the operator accepts the broader runtime semantics.
161+162+When approvals are required, the exec tool returns immediately with an approval id. Use that id to
163+correlate later system events (`Exec finished` / `Exec denied`). If no decision arrives before the
164+timeout, the request is treated as an approval timeout and surfaced as a denial reason.
165+166+### Followup delivery behavior
167+168+After an approved async exec finishes, OpenClaw sends a followup `agent` turn to the same session.
169+170+- If a valid external delivery target exists (deliverable channel plus target `to`), followup delivery uses that channel.
171+- In webchat-only or internal-session flows with no external target, followup delivery stays session-only (`deliver: false`).
172+- If a caller explicitly requests strict external delivery with no resolvable external channel, the request fails with `INVALID_REQUEST`.
173+- If `bestEffortDeliver` is enabled and no external channel can be resolved, delivery is downgraded to session-only instead of failing.
174+175+## Approval forwarding to chat channels
176+177+You can forward exec approval prompts to any chat channel (including plugin channels) and approve
178+them with `/approve`. This uses the normal outbound delivery pipeline.
179+180+Config:
181+182+```json5
183+{
184+ approvals: {
185+ exec: {
186+ enabled: true,
187+ mode: "session", // "session" | "targets" | "both"
188+ agentFilter: ["main"],
189+ sessionFilter: ["discord"], // substring or regex
190+ targets: [
191+ { channel: "slack", to: "U12345678" },
192+ { channel: "telegram", to: "123456789" },
193+ ],
194+ },
195+ },
196+}
197+```
198+199+Reply in chat:
200+201+```
202+/approve <id> allow-once
203+/approve <id> allow-always
204+/approve <id> deny
205+```
206+207+The `/approve` command handles both exec approvals and plugin approvals. If the ID does not match a pending exec approval, it automatically checks plugin approvals instead.
208+209+### Plugin approval forwarding
210+211+Plugin approval forwarding uses the same delivery pipeline as exec approvals but has its own
212+independent config under `approvals.plugin`. Enabling or disabling one does not affect the other.
213+214+```json5
215+{
216+ approvals: {
217+ plugin: {
218+ enabled: true,
219+ mode: "targets",
220+ agentFilter: ["main"],
221+ targets: [
222+ { channel: "slack", to: "U12345678" },
223+ { channel: "telegram", to: "123456789" },
224+ ],
225+ },
226+ },
227+}
228+```
229+230+The config shape is identical to `approvals.exec`: `enabled`, `mode`, `agentFilter`,
231+`sessionFilter`, and `targets` work the same way.
232+233+Channels that support shared interactive replies render the same approval buttons for both exec and
234+plugin approvals. Channels without shared interactive UI fall back to plain text with `/approve`
235+instructions.
236+237+### Same-chat approvals on any channel
238+239+When an exec or plugin approval request originates from a deliverable chat surface, the same chat
240+can now approve it with `/approve` by default. This applies to channels such as Slack, Matrix, and
241+Microsoft Teams in addition to the existing Web UI and terminal UI flows.
242+243+This shared text-command path uses the normal channel auth model for that conversation. If the
244+originating chat can already send commands and receive replies, approval requests no longer need a
245+separate native delivery adapter just to stay pending.
246+247+Discord and Telegram also support same-chat `/approve`, but those channels still use their
248+resolved approver list for authorization even when native approval delivery is disabled.
249+250+For Telegram and other native approval clients that call the Gateway directly,
251+this fallback is intentionally bounded to "approval not found" failures. A real
252+exec approval denial/error does not silently retry as a plugin approval.
253+254+### Native approval delivery
255+256+Some channels can also act as native approval clients. Native clients add approver DMs, origin-chat
257+fanout, and channel-specific interactive approval UX on top of the shared same-chat `/approve`
258+flow.
259+260+When native approval cards/buttons are available, that native UI is the primary
261+agent-facing path. The agent should not also echo a duplicate plain chat
262+`/approve` command unless the tool result says chat approvals are unavailable or
263+manual approval is the only remaining path.
264+265+Generic model:
266+267+- host exec policy still decides whether exec approval is required
268+- `approvals.exec` controls forwarding approval prompts to other chat destinations
269+- `channels.<channel>.execApprovals` controls whether that channel acts as a native approval client
270+271+Native approval clients auto-enable DM-first delivery when all of these are true:
272+273+- the channel supports native approval delivery
274+- approvers can be resolved from explicit `execApprovals.approvers` or that
275+ channel's documented fallback sources
276+- `channels.<channel>.execApprovals.enabled` is unset or `"auto"`
277+278+Set `enabled: false` to disable a native approval client explicitly. Set `enabled: true` to force
279+it on when approvers resolve. Public origin-chat delivery stays explicit through
280+`channels.<channel>.execApprovals.target`.
281+282+FAQ: [Why are there two exec approval configs for chat approvals?](/help/faq#why-are-there-two-exec-approval-configs-for-chat-approvals)
283+284+- Discord: `channels.discord.execApprovals.*`
285+- Slack: `channels.slack.execApprovals.*`
286+- Telegram: `channels.telegram.execApprovals.*`
287+288+These native approval clients add DM routing and optional channel fanout on top of the shared
289+same-chat `/approve` flow and shared approval buttons.
290+291+Shared behavior:
292+293+- Slack, Matrix, Microsoft Teams, and similar deliverable chats use the normal channel auth model
294+ for same-chat `/approve`
295+- when a native approval client auto-enables, the default native delivery target is approver DMs
296+- for Discord and Telegram, only resolved approvers can approve or deny
297+- Discord approvers can be explicit (`execApprovals.approvers`) or inferred from `commands.ownerAllowFrom`
298+- Telegram approvers can be explicit (`execApprovals.approvers`) or inferred from existing owner config (`allowFrom`, plus direct-message `defaultTo` where supported)
299+- Slack approvers can be explicit (`execApprovals.approvers`) or inferred from `commands.ownerAllowFrom`
300+- Slack native buttons preserve approval id kind, so `plugin:` ids can resolve plugin approvals
301+ without a second Slack-local fallback layer
302+- Matrix native DM/channel routing and reaction shortcuts handle both exec and plugin approvals;
303+ plugin authorization still comes from `channels.matrix.dm.allowFrom`
304+- the requester does not need to be an approver
305+- the originating chat can approve directly with `/approve` when that chat already supports commands and replies
306+- native Discord approval buttons route by approval id kind: `plugin:` ids go
307+ straight to plugin approvals, everything else goes to exec approvals
308+- native Telegram approval buttons follow the same bounded exec-to-plugin fallback as `/approve`
309+- when native `target` enables origin-chat delivery, approval prompts include the command text
310+- pending exec approvals expire after 30 minutes by default
311+- if no operator UI or configured approval client can accept the request, the prompt falls back to `askFallback`
312+313+Telegram defaults to approver DMs (`target: "dm"`). You can switch to `channel` or `both` when you
314+want approval prompts to appear in the originating Telegram chat/topic as well. For Telegram forum
315+topics, OpenClaw preserves the topic for the approval prompt and the post-approval follow-up.
316+317+See:
318+319+- [Discord](/channels/discord)
320+- [Telegram](/channels/telegram)
321+322+### macOS IPC flow
323+324+```
325+Gateway -> Node Service (WS)
326+ | IPC (UDS + token + HMAC + TTL)
327+ v
328+ Mac App (UI + approvals + system.run)
329+```
330+331+Security notes:
332+333+- Unix socket mode `0600`, token stored in `exec-approvals.json`.
334+- Same-UID peer check.
335+- Challenge/response (nonce + HMAC token + request hash) + short TTL.
336+337+## Related
338+339+- [Exec approvals](/tools/exec-approvals) — core policy and approval flow
340+- [Exec tool](/tools/exec)
341+- [Elevated mode](/tools/elevated)
342+- [Skills](/tools/skills) — skill-backed auto-allow behavior
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。