diff --git a/docs/cli/node.md b/docs/cli/node.md index a7c967325f4..dbdfe15e891 100644 --- a/docs/cli/node.md +++ b/docs/cli/node.md @@ -130,3 +130,8 @@ The node host stores its node id, token, display name, and gateway connection in - `~/.openclaw/exec-approvals.json` - [Exec approvals](/tools/exec-approvals) - `openclaw approvals --node ` (edit from the Gateway) + +For approved async node exec, OpenClaw prepares a canonical `systemRunPlan` +before prompting. The later approved `system.run` forward reuses that stored +plan, so edits to command/cwd/session fields after the approval request was +created are rejected instead of changing what the node executes. diff --git a/docs/gateway/protocol.md b/docs/gateway/protocol.md index 7703c1485f6..5762b06b5d7 100644 --- a/docs/gateway/protocol.md +++ b/docs/gateway/protocol.md @@ -226,6 +226,11 @@ The Gateway treats these as **claims** and enforces server-side allowlists. - When an exec request needs approval, the gateway broadcasts `exec.approval.requested`. - Operator clients resolve by calling `exec.approval.resolve` (requires `operator.approvals` scope). - For `host=node`, `exec.approval.request` must include `systemRunPlan` (canonical `argv`/`cwd`/`rawCommand`/session metadata). Requests missing `systemRunPlan` are rejected. +- After approval, forwarded `node.invoke system.run` calls reuse that canonical + `systemRunPlan` as the authoritative command/cwd/session context. +- If a caller mutates `command`, `rawCommand`, `cwd`, `agentId`, or + `sessionKey` between prepare and the final approved `system.run` forward, the + gateway rejects the run instead of trusting the mutated payload. ## Agent delivery fallback diff --git a/docs/nodes/index.md b/docs/nodes/index.md index f83b168ee6a..2aa5512a937 100644 --- a/docs/nodes/index.md +++ b/docs/nodes/index.md @@ -334,6 +334,9 @@ Notes: - `system.run` returns stdout/stderr/exit code in the payload. - Shell execution now goes through the `exec` tool with `host=node`; `nodes` remains the direct-RPC surface for explicit node commands. - `nodes invoke` does not expose `system.run` or `system.run.prepare`; those stay on the exec path only. +- The exec path prepares a canonical `systemRunPlan` before approval. Once an + approval is granted, the gateway forwards that stored plan, not any later + caller-edited command/cwd/session fields. - `system.notify` respects notification permission state on the macOS app. - Unrecognized node `platform` / `deviceFamily` metadata uses a conservative default allowlist that excludes `system.run` and `system.which`. If you intentionally need those commands for an unknown platform, add them explicitly via `gateway.nodes.allowCommands`. - `system.run` supports `--cwd`, `--env KEY=VAL`, `--command-timeout`, and `--needs-screen-recording`. diff --git a/docs/tools/exec-approvals.md b/docs/tools/exec-approvals.md index 6e7b306640d..45e2b6f9487 100644 --- a/docs/tools/exec-approvals.md +++ b/docs/tools/exec-approvals.md @@ -371,6 +371,16 @@ For `host=node`, approval requests include a canonical `systemRunPlan` payload. that plan as the authoritative command/cwd/session context when forwarding approved `system.run` requests. +That matters for async approval latency: + +- the node exec path prepares one canonical plan up front +- the approval record stores that plan and its binding metadata +- once approved, the final forwarded `system.run` call reuses the stored plan + instead of trusting later caller edits +- if the caller changes `command`, `rawCommand`, `cwd`, `agentId`, or + `sessionKey` after the approval request was created, the gateway rejects the + forwarded run as an approval mismatch + ## Interpreter/runtime commands Approval-backed interpreter/runtime runs are intentionally conservative: