Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
445
docs/tools/exec-approvals-advanced.md
Normal file
445
docs/tools/exec-approvals-advanced.md
Normal file
@@ -0,0 +1,445 @@
|
||||
---
|
||||
summary: "Advanced exec approvals: safe bins, interpreter binding, approval forwarding, native delivery"
|
||||
read_when:
|
||||
- Configuring safe bins or custom safe-bin profiles
|
||||
- Forwarding approvals to Slack/Discord/Telegram or other chat channels
|
||||
- Implementing a native approval client for a channel
|
||||
title: "Exec approvals — advanced"
|
||||
---
|
||||
|
||||
Advanced exec-approval topics: the `safeBins` fast-path, interpreter/runtime
|
||||
binding, and approval-forwarding to chat channels (including native delivery).
|
||||
For the core policy and approval flow, see [Exec approvals](/tools/exec-approvals).
|
||||
|
||||
## Safe bins (stdin-only)
|
||||
|
||||
`tools.exec.safeBins` names **stdin-only** binaries (for example `cut`) that
|
||||
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.
|
||||
|
||||
<Warning>
|
||||
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>`.
|
||||
</Warning>
|
||||
|
||||
Default safe bins:
|
||||
|
||||
[//]: # "SAFE_BIN_DEFAULTS:START"
|
||||
|
||||
`cut`, `uniq`, `head`, `tail`, `tr`, `wc`
|
||||
|
||||
[//]: # "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.
|
||||
|
||||
### Argv validation and denied flags
|
||||
|
||||
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 validate 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. `awk`
|
||||
and `sed` are always denied as safe bins (their semantics cannot be validated
|
||||
to stdin-only); `jq` can be opted in, but OpenClaw still rejects `env`-style
|
||||
filters (for example `jq env` or `jq -n env`) in safe-bin mode so `jq` cannot
|
||||
dump the host process environment without an explicit allowlist path or
|
||||
approval prompt.
|
||||
|
||||
### Trusted binary directories
|
||||
|
||||
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, wrappers, and multiplexers
|
||||
|
||||
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, transparent dispatch wrappers
|
||||
(for example `env`, `flock`, `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
|
||||
|
||||
| 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, or bare command-name glob for PATH-invoked commands |
|
||||
| Argument scope | Restricted by safe-bin profile and literal-token rules | Path match by default; optional `argPattern` can restrict parsed argv |
|
||||
| Typical examples | `head`, `tail`, `tr`, `wc` | `jq`, `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`).
|
||||
- `safeBinTrustedDirs` comes from config (`tools.exec.safeBinTrustedDirs` or per-agent `agents.list[].tools.exec.safeBinTrustedDirs`).
|
||||
- `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 the host-local approvals file under `agents.<id>.allowlist` (or via Control UI / `openclaw approvals allowlist ...`).
|
||||
- `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.
|
||||
|
||||
Custom profile example:
|
||||
|
||||
```json5
|
||||
{
|
||||
tools: {
|
||||
exec: {
|
||||
safeBins: ["jq", "myfilter"],
|
||||
safeBinProfiles: {
|
||||
myfilter: {
|
||||
minPositional: 0,
|
||||
maxPositional: 0,
|
||||
allowedValueFlags: ["-n", "--limit"],
|
||||
deniedFlags: ["-f", "--file", "-c", "--command"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Interpreter/runtime commands
|
||||
|
||||
Approval-backed interpreter/runtime runs are intentionally conservative:
|
||||
|
||||
- Exact argv/cwd/env context is always bound.
|
||||
- Direct shell script and direct runtime file forms are best-effort bound to one concrete local
|
||||
file snapshot.
|
||||
- Common package-manager wrapper forms that still resolve to one direct local file (for example
|
||||
`pnpm exec`, `pnpm node`, `npm exec`, `npx`) are unwrapped before binding.
|
||||
- If OpenClaw cannot identify exactly one concrete local file for an interpreter/runtime command
|
||||
(for example package scripts, eval forms, runtime-specific loader chains, or ambiguous multi-file
|
||||
forms), approval-backed execution is denied instead of claiming semantic coverage it does not
|
||||
have.
|
||||
- For those workflows, prefer sandboxing, a separate host boundary, or an explicit trusted
|
||||
allowlist/full workflow where the operator accepts the broader runtime semantics.
|
||||
|
||||
When approvals are required, the exec tool returns immediately with an approval id. Use that id to
|
||||
correlate later approved-run system events (`Exec finished`, and `Exec running` when configured).
|
||||
If no decision arrives before the timeout, the request is treated as an approval timeout and
|
||||
surfaced as a terminal host-command denial. For main-agent async approvals with an originating
|
||||
session, OpenClaw also resumes that session with an internal followup so the agent observes that
|
||||
the command did not run instead of later repairing a missing result. Pending exec approvals expire
|
||||
after 30 minutes by default.
|
||||
|
||||
### Followup delivery behavior
|
||||
|
||||
After an approved async exec finishes, OpenClaw sends a followup `agent` turn to the same session.
|
||||
Denied async approvals use the same main-session followup path for the denial status, but they do
|
||||
not register elevated runtime handoffs and they do not run the command. Denials without a resumable
|
||||
main session are either suppressed or reported through a safe direct route when one exists.
|
||||
|
||||
- If a valid external delivery target exists (deliverable channel plus target `to`), followup delivery uses that channel.
|
||||
- In webchat-only or internal-session flows with no external target, followup delivery stays session-only (`deliver: false`).
|
||||
- If a caller explicitly requests strict external delivery with no resolvable external channel, the request fails with `INVALID_REQUEST`.
|
||||
- If `bestEffortDeliver` is enabled and no external channel can be resolved, delivery is downgraded to session-only instead of failing.
|
||||
|
||||
## 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:
|
||||
|
||||
```json5
|
||||
{
|
||||
approvals: {
|
||||
exec: {
|
||||
enabled: true,
|
||||
mode: "session", // "session" | "targets" | "both"
|
||||
agentFilter: ["main"],
|
||||
sessionFilter: ["discord"], // substring or regex
|
||||
targets: [
|
||||
{ channel: "slack", to: "U12345678" },
|
||||
{ channel: "telegram", to: "123456789" },
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Reply in chat:
|
||||
|
||||
```
|
||||
/approve <id> allow-once
|
||||
/approve <id> allow-always
|
||||
/approve <id> deny
|
||||
```
|
||||
|
||||
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. This fallback is bounded to "approval not found" failures; a real exec approval denial/error does not silently retry as a plugin approval.
|
||||
|
||||
### Plugin approval forwarding
|
||||
|
||||
Plugin approval forwarding uses the same delivery pipeline as exec approvals but has its own
|
||||
independent config under `approvals.plugin`. Enabling or disabling one does not affect the other.
|
||||
For plugin-authoring behavior, request fields, and decision semantics, see
|
||||
[Plugin permission requests](/plugins/plugin-permission-requests).
|
||||
|
||||
```json5
|
||||
{
|
||||
approvals: {
|
||||
plugin: {
|
||||
enabled: true,
|
||||
mode: "targets",
|
||||
agentFilter: ["main"],
|
||||
targets: [
|
||||
{ channel: "slack", to: "U12345678" },
|
||||
{ channel: "telegram", to: "123456789" },
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The config shape is identical to `approvals.exec`: `enabled`, `mode`, `agentFilter`,
|
||||
`sessionFilter`, and `targets` work the same way.
|
||||
|
||||
Channels that support shared interactive replies render the same approval buttons for both exec and
|
||||
plugin approvals. Channels without shared interactive UI fall back to plain text with `/approve`
|
||||
instructions. Plugin approval requests may restrict the available decisions: approval surfaces use
|
||||
the request's declared decision set, and the Gateway rejects attempts to submit a decision that was
|
||||
not offered.
|
||||
|
||||
### Same-chat approvals on any channel
|
||||
|
||||
When an exec or plugin approval request originates from a deliverable chat surface, that same chat
|
||||
can approve it with `/approve` by default. This applies to Slack, Matrix, Microsoft Teams, and
|
||||
similar deliverable chats, in addition to the existing Web UI and terminal UI flows, using the
|
||||
normal channel auth model for that conversation. If the originating chat can already send commands
|
||||
and receive replies, approval requests no longer need a separate native delivery adapter just to
|
||||
stay pending.
|
||||
|
||||
Discord, Telegram, and QQ bot also support same-chat `/approve`, but those channels still use their
|
||||
resolved approver list for authorization even when native approval delivery is disabled.
|
||||
|
||||
### Native approval delivery
|
||||
|
||||
Some channels can also act as native approval clients: Discord, Slack, Telegram, Matrix, and QQ bot.
|
||||
Native clients add approver DMs, origin-chat fanout, and channel-specific interactive approval UX on
|
||||
top of the shared same-chat `/approve` flow.
|
||||
|
||||
When native approval cards/buttons are available, that native UI is the primary agent-facing path.
|
||||
The agent should not also echo a duplicate plain chat `/approve` command unless the tool result says
|
||||
chat approvals are unavailable or manual approval is the only remaining path.
|
||||
|
||||
If a native approval client is configured but no native runtime is active for the originating
|
||||
channel, OpenClaw keeps the local deterministic `/approve` prompt visible. If the native runtime is
|
||||
active and attempts delivery but no target receives the card, OpenClaw sends a same-chat fallback
|
||||
notice with the exact `/approve <id> <decision>` command so the request can still be resolved.
|
||||
|
||||
Generic model:
|
||||
|
||||
- host exec policy still decides whether exec approval is required
|
||||
- `approvals.exec` controls forwarding approval prompts to other chat destinations
|
||||
- `channels.<channel>.execApprovals` controls whether Discord, Slack, Telegram, QQ bot, and similar
|
||||
channel-specific native clients are enabled
|
||||
- Slack plugin approvals can use Slack's native approval client when the request comes from Slack
|
||||
and Slack plugin approvers resolve; `approvals.plugin` can also route plugin approvals to Slack
|
||||
sessions or targets even when Slack exec approvals are disabled
|
||||
- Google Chat native approval cards handle exec and plugin approvals that originate from Google
|
||||
Chat spaces or threads when stable `users/<id>` approvers resolve from `dm.allowFrom` or
|
||||
`defaultTo`; they do not use reaction events for decisions
|
||||
- WhatsApp and Signal reaction approval delivery are gated by `approvals.exec` and
|
||||
`approvals.plugin`; they do not have `channels.<channel>.execApprovals` blocks
|
||||
|
||||
Native approval clients auto-enable DM-first delivery when all of these are true:
|
||||
|
||||
- the channel supports native approval delivery
|
||||
- approvers can be resolved from explicit `execApprovals.approvers` or owner
|
||||
identity such as `commands.ownerAllowFrom`
|
||||
- `channels.<channel>.execApprovals.enabled` is unset or `"auto"`
|
||||
|
||||
Set `enabled: false` to disable a native approval client explicitly. Set `enabled: true` to force
|
||||
it on when approvers resolve. Public origin-chat delivery stays explicit through
|
||||
`channels.<channel>.execApprovals.target`. When native `target` enables origin-chat delivery,
|
||||
approval prompts include the command text.
|
||||
|
||||
FAQ: [Why are there two exec approval configs for chat approvals?](/help/faq-first-run)
|
||||
|
||||
- Discord: `channels.discord.execApprovals.*`
|
||||
- Slack: `channels.slack.execApprovals.*`
|
||||
- Telegram: `channels.telegram.execApprovals.*`
|
||||
- QQ bot: `channels.qqbot.execApprovals.*`
|
||||
- Google Chat: configure stable approvers with `channels.googlechat.dm.allowFrom` or
|
||||
`channels.googlechat.defaultTo`; no `execApprovals` block is required
|
||||
- WhatsApp: use `approvals.exec` and `approvals.plugin` to route approval prompts to WhatsApp
|
||||
- Signal: use `approvals.exec` and `approvals.plugin` to route approval prompts to Signal
|
||||
|
||||
Native-client-specific routing:
|
||||
|
||||
- Telegram defaults to approver DMs (`target: "dm"`). Switch to `channel` or `both` to also show
|
||||
approval prompts in the originating Telegram chat/topic. For Telegram forum topics, OpenClaw
|
||||
preserves the topic for the approval prompt and the post-approval follow-up.
|
||||
- Discord and Telegram approvers can be explicit (`execApprovals.approvers`) or inferred from
|
||||
`commands.ownerAllowFrom`; only resolved approvers can approve or deny.
|
||||
- Slack approvers can be explicit (`execApprovals.approvers`) or inferred from
|
||||
`commands.ownerAllowFrom`. Slack plugin approval DMs use Slack plugin approvers from `allowFrom`
|
||||
and account default routing, not Slack exec approvers. Slack native buttons preserve approval id
|
||||
kind, so `plugin:` ids can resolve plugin approvals without a second Slack-local fallback layer.
|
||||
- Google Chat native cards preserve the manual `/approve` fallback in message text, but card button
|
||||
callbacks carry only opaque action tokens; the approval id and decision are recovered from
|
||||
server-side pending state.
|
||||
- WhatsApp emoji approvals handle both exec and plugin prompts only when the matching top-level
|
||||
forwarding family is enabled and routes to WhatsApp; target-only WhatsApp forwarding stays on the
|
||||
shared forwarding path unless it matches the same native origin target.
|
||||
- Signal reaction approvals handle both exec and plugin prompts only when the matching top-level
|
||||
forwarding family is enabled and routes to Signal. Direct same-chat Signal exec approvals can
|
||||
suppress the local `/approve` fallback without explicit approvers; Signal reaction resolution
|
||||
still requires explicit Signal approvers from `channels.signal.allowFrom` or `defaultTo`.
|
||||
- Matrix native DM/channel routing and reaction shortcuts handle both exec and plugin approvals;
|
||||
plugin authorization still comes from `channels.matrix.dm.allowFrom`. Matrix native prompts
|
||||
include `com.openclaw.approval` custom event content on the first prompt event so OpenClaw-aware
|
||||
Matrix clients can read structured approval state while stock clients keep the plain-text
|
||||
`/approve` fallback.
|
||||
- Native Discord approval buttons route by approval id kind: `plugin:` ids go straight to plugin
|
||||
approvals, everything else goes to exec approvals. Native Telegram approval buttons follow the
|
||||
same bounded exec-to-plugin fallback as `/approve`.
|
||||
- The requester does not need to be an approver.
|
||||
- If no operator UI or configured approval client can accept the request, the prompt falls back to
|
||||
`askFallback`.
|
||||
|
||||
Sensitive owner-only group commands such as `/diagnostics` and `/export-trajectory` use private
|
||||
owner routing for approval prompts and final results. OpenClaw first tries a private route on the
|
||||
same surface where the owner ran the command. If that surface has no private owner route, it falls
|
||||
back to the first available owner route from `commands.ownerAllowFrom`, so a Discord group command
|
||||
can still send the approval and result to the owner's Telegram DM when Telegram is the configured
|
||||
primary private interface. The group chat only gets a short acknowledgement.
|
||||
|
||||
See:
|
||||
|
||||
- [Discord](/channels/discord)
|
||||
- [Telegram](/channels/telegram)
|
||||
- [QQ bot](/channels/qqbot)
|
||||
|
||||
### macOS IPC flow
|
||||
|
||||
```
|
||||
Gateway -> Node Service (WS)
|
||||
| IPC (UDS + token + HMAC + TTL)
|
||||
v
|
||||
Mac App (UI + approvals + system.run)
|
||||
```
|
||||
|
||||
Security notes:
|
||||
|
||||
- Unix socket mode `0600`, token stored in `exec-approvals.json`.
|
||||
- Same-UID peer check.
|
||||
- Challenge/response (nonce + HMAC token + request hash) + short TTL.
|
||||
|
||||
## FAQ
|
||||
|
||||
### When would `accountId` and `threadId` be used on an approval target?
|
||||
|
||||
Use `accountId` when the channel has multiple configured identities and the approval prompt must
|
||||
leave through one specific account. Use `threadId` when the destination supports topics or
|
||||
threads and the prompt should stay inside that thread instead of the top-level chat.
|
||||
|
||||
A concrete Telegram case is an operations supergroup with forum topics and two Telegram bot
|
||||
accounts. The `to` value names the supergroup, `accountId` selects the bot account, and `threadId`
|
||||
selects the forum topic:
|
||||
|
||||
```json5
|
||||
{
|
||||
approvals: {
|
||||
exec: {
|
||||
enabled: true,
|
||||
mode: "targets",
|
||||
targets: [
|
||||
{
|
||||
channel: "telegram",
|
||||
to: "-1001234567890",
|
||||
accountId: "ops-bot",
|
||||
threadId: "77",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
telegram: {
|
||||
accounts: {
|
||||
default: {
|
||||
name: "Primary bot",
|
||||
botToken: "env:TELEGRAM_PRIMARY_BOT_TOKEN",
|
||||
},
|
||||
"ops-bot": {
|
||||
name: "Operations bot",
|
||||
botToken: "env:TELEGRAM_OPS_BOT_TOKEN",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
With that setup, forwarded exec approvals are posted by the `ops-bot` Telegram account into topic
|
||||
`77` of chat `-1001234567890`. A target without `accountId` uses the channel's default account, and
|
||||
a target without `threadId` posts to the top-level destination.
|
||||
|
||||
### When approvals are sent to a session, can anyone in that session approve them?
|
||||
|
||||
No. Session delivery only controls where the prompt appears. It does not by itself authorize every
|
||||
participant in that chat to approve.
|
||||
|
||||
For generic same-chat `/approve`, the sender must already be authorized for commands in that
|
||||
channel session. If the channel exposes explicit approval approvers, those approvers can authorize
|
||||
the `/approve` action even when they are not otherwise command-authorized in that session.
|
||||
|
||||
Some channels are stricter. Discord, Telegram, Matrix, Slack native approval DMs, and similar
|
||||
native approval clients use their resolved approver lists for approval authorization. For example,
|
||||
a Telegram forum-topic approval prompt can be visible to everyone in the topic, but only numeric
|
||||
Telegram user IDs resolved from `channels.telegram.execApprovals.approvers` or
|
||||
`commands.ownerAllowFrom` can approve or deny it.
|
||||
|
||||
## Related
|
||||
|
||||
- [Exec approvals](/tools/exec-approvals) — core policy and approval flow
|
||||
- [Exec tool](/tools/exec)
|
||||
- [Elevated mode](/tools/elevated)
|
||||
- [Skills](/tools/skills) — skill-backed auto-allow behavior
|
||||
Reference in New Issue
Block a user