fix(chat): preserve history cache correlation - #7
Closed
bkudiess wants to merge 203 commits into
Closed
Conversation
Adds app-managed WSL gateway recovery actions to the setup wizard, moves shared WSL gateway helpers into OpenClaw.Connection, and documents the recovery behavior.
Fixes the Settings hotkey to open the Settings page and refreshes the MXC helper restore so stale helper binaries are not reused while keeping npm restore incremental.
Ports the MXC probe/SDK 0.7.0 work and inbound pairing approval dialog from the mistaken master branch target onto main. Maintainer repair included: - Preserving non-actionable handling for ambiguous legacy pairing fallback IDs in the new approval queue. - Adding regression coverage for device and node fallback ambiguity. - Version-stamping MXC SDK restore so stale node_modules cannot satisfy the 0.7.0 port. Validation: - Local .\build.ps1 passed. - Local Shared tests passed: 2411 passed / 29 skipped. - Local Tray tests passed: 1127 passed. - Local Connection tests passed: 373 passed. - GitHub CI passed on repaired head 7be0688: test, E2E setup-connect, E2E revocation-recovery, E2E network-recovery, build win-x64, build win-arm64, repo-hygiene. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Routes local system.run approval prompts into native chat when an interactive chat surface is available, while preserving the native approval dialog fallback. Maintainer updates: - Rebased onto current main after the MXC/pairing port landed. - Kept the deleted Chat Explorations fake provider removed during rebase. - Removed a duplicate ChatWindow.Show() call introduced during the branch hardening pass. Validation: - Local .\build.ps1 passed. - Local Shared tests passed: 2417 passed / 29 skipped. - Local Tray tests passed: 1154 passed. - Independent code review found only the duplicate Show() issue, now fixed. - Independent security review found no concrete security vulnerabilities. - GitHub CI passed on repaired head ae301c0: repo-hygiene, test, E2E setup-connect, E2E revocation-recovery, E2E network-recovery, build win-x64, build win-arm64, Socket Security. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Merged after daily triage: focused scope, green CI, and low-risk validation evidence.
Merged after daily triage: focused scope, green CI, and low-risk validation evidence.
Merged after daily triage: focused scope, green CI, and low-risk validation evidence.
Merged after rerunning the failed setup-connect shard successfully; follow-on win-x64 and win-arm64 builds also passed.
Adds a static gateway protocol snapshot guard covering sessions, files, commands, and compaction, with maintainer repair so no-parameter used request methods are also checked for payload drift.
Improves Cron History so users can inspect full sanitized run responses, preserves expanded/running state while refreshing, adds response-aware cron.run feedback, and includes compatibility coverage for legacy cron.run payload fallback.
Route selected background issues into the in-app Notifications page, including connection, sandbox, channel, pairing, and cron failures. Partially addresses openclaw#820; it does not close the broader notification-routing issue.
* Make A2UI dataModelUpdate.valueArray first-class The v0.8 `dataModelUpdate.valueArray` typed value was silently dropped by the WinUI parser: `DataModelEntry` had no `ValueArray` field and `ToJsonNode()` returned null for it. Only `valueString/Number/Boolean/valueMap` were handled. This broke seeding an array into a surface's data model (e.g. a multi-select `MultipleChoice` bound to a path), even though `valueArray` is part of the v0.8 protocol (docs/a2ui/protocol.md §2.2 and data-and-actions.md). Changes: - Parser (A2UIProtocol.cs): add `DataModelEntry.ValueArray`; `ToJsonNode()` emits a `JsonArray`; new `ParseValueArray`/`ParseArrayElement` handle value-typed object elements, bare primitives (`["a",1,true]`), nested maps/arrays, and preserve JSON null as a stable index slot. - DoS guard (DataModelStore.cs): the 32-deep depth bound now recurses `valueArray` as well as `valueMap`. - Security (SecretRedactor.cs): the array branch now redacts registered/ denylisted element paths instead of only recursing, so a secret seeded into an array (e.g. an obscured field bound to /codes/0) no longer leaks via canvas.a2ui.dump. - Docs (SKILL.md): correct the stale "arrays are not first-class" note. Tests: parser coverage for valueArray (strings, mixed scalars, maps, nested arrays, bare primitives, empty, null slots) plus valueMap/scalar regressions; secret-redaction regressions for secrets inside arrays; store-level base-path landing and depth-guard rejection; and an end-to-end MultipleChoice surface seeded via valueArray that asserts both the snapshot and the rendered preselection. Validation: build.ps1 (all projects), Shared.Tests, Tray.Tests, and A2UI UITests all pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix valueArray redaction and notifications Reject non-canonical array pointer indices for data-model array access, notify descendant subscribers when container values are replaced, and redact registered secret descendants when action contexts or snapshots include parent paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Scott Hanselman <scott@hanselman.com>
* Fix canvas navigation URL handling * Fix canvas navigate dispatcher timeout Route canvas.navigate through the same dispatcher timeout guard used by canvas.eval and canvas.snapshot so stalled UI dispatch cannot hang MCP tool calls indefinitely. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Scott Hanselman <scott@hanselman.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add TTS provider fallback and tts.status readiness command When the requested/configured TTS provider isn't usable (no ElevenLabs key, Piper voice not downloaded), gracefully fall back to Windows TTS instead of throwing, and report the effective vs requested provider plus a fellBack flag. A stale configured Windows voice now degrades to the system default voice so the fallback always speaks. Add a tts.status node command that reports per-provider readiness (ready/needs-api-key/needs-voice/voice-not-downloaded/unavailable) plus the configured/effective provider and willFallBack. PII-free. Wired across DangerousCommands gating, MCP descriptions, skill.md, and the setup allowlist export. Fix a stale pre-GA SHA-256 TODO docstring in PiperVoiceManager (verification is already implemented and enforced by AssetHashPinningTests). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Preserve configured TTS fallback for chat playback Leave chat playback provider unset so TextToSpeechService can distinguish configured/default playback from explicit provider requests and apply the new Windows fallback when the configured provider is unavailable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Scott Hanselman <scott@hanselman.com>
Validate screen.snapshot image formats before invoking the capture backend, normalize jpg to jpeg, and derive the response data URI MIME type from the validated format instead of the backend echo. Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…w#824) * Wizard protocol resilience: progress/non-interactive steps, auth prompts, restart hardening Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Harden wizard progress polling budget Allow a single long-running gateway progress step to use the full bounded progress budget instead of failing after the previous shorter per-step cap. Share the progress constants between the setup runner and UI and cover the intended budget in tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Scott Hanselman <scott@hanselman.com>
Measure the tray popup against its final client width and apply pixel-exact window sizing so wrapped content determines height deterministically. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Normalize the Sandbox page toggle off when MXC is definitively unavailable, reject turning it back on in the host-fallback mode, and preserve strict fallback blocking so users who opted into command denial remain protected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Squashed from PR openclaw#800 after maintainer review and local fixes.\n\nLocal validation:\n- .\\build.ps1\n- dotnet test .\\tests\\OpenClaw.Shared.Tests\\OpenClaw.Shared.Tests.csproj --no-restore\n- dotnet test .\\tests\\OpenClaw.Tray.Tests\\OpenClaw.Tray.Tests.csproj --no-restore\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test: add Gateway MXC runtime E2E proof * test: probe MXC E2E gate with discovered wxc-exec * test: extend Gateway MXC E2E timeout budgets * test: avoid blocked redirection in Gateway MXC deny proof * test: prove MXC denied copy reaches destination * test: document serialized MXC E2E collection gate * test: raise MXC E2E sandbox timeout cap * test: approve MXC denied-write copy proof step * test: sync MXC approval proof diagnostics * test: narrow MXC exec approval proof policy * ci: extend setup-connect E2E timeout Give only the setup-connect E2E shard extra time for the combined setup and MXC runtime proof fixtures while leaving the other E2E shards on the existing budget. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: omit unsupported MXC denied paths Windows wxc-exec rejects filesystem.deniedPaths before launching the sandboxed command. Keep denied paths as the local allow-list filter, but omit the unsupported backend field so AppContainer default-deny can run the command while still stripping parent/exact grants over sensitive paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Vitor Cepeda Lopes <7040636+TheAngryPit@users.noreply.github.com> Co-authored-by: Scott Hanselman <scott@hanselman.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
) * Improve remote gateway setup and connection error recovery Add RemoteGatewayClassifier + GatewayErrorClassifier in OpenClaw.Shared and wire ConnectionPage remote setup advice, scope/token-drift/TLS recovery, and localized strings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Clear stale SSH fields for setup-code flow Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Scott Hanselman <scott@hanselman.com>
* Node mode UI: surface MCP-only/connecting states and repair gateway-node gating Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix MCP-only node status visibility Surface the local MCP-only node card even when no gateway/operator session exists, and make the reconnect-backoff test wait for server-side accept publication. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Keep MCP-only mode from joining gateways Gate the post-operator local NodeService auto-connect on EnableNodeMode so local MCP-only serving does not create gateway node pairing requests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Request node reconnect in shared-token setup E2E The shared-token setup path can now remain MCP-only after operator approval, so the E2E needs to request node reconnect explicitly before waiting for a node credential. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Scott Hanselman <scott@hanselman.com>
) A connection/auth failure rendered two error bars: the global top-window InfoBar plus an in-page "Connection Error" InfoBar on the Connection page. The auth pipeline also published two global notifications for one failure, which forced the banner action to degrade to "Show more". Remove the in-page AuthErrorBar and consolidate to one top banner that: - carries the gateway/node error states the in-page bar covered - always routes the user to the Connection page via an "Open Connection" action (connection notifications are prioritized as the visible banner, preferring an actionable one over an action-less transient) - renders as a single thin line: bold headline + " - detail", with the action as a right-aligned hyperlink (Windows-aligned InfoBar) Other changes: - Stop publishing the duplicate connection:authentication-failed banner; the snapshot-driven connection:issue notification is the single source. - Re-home transient errors: WSL host-action failures use the inline card status (or a top notification when the card is hidden); connect/switch failures publish via ShowTransientConnectionError on the same banner id. - Remove now-dead resw strings (ConnectionPage_AuthGuidance*, ConnectionPage_ConnectFailed) across all five locales. Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add tray live dashboard glance Add a compact status summary to the tray flyout that surfaces gateway health, refresh freshness, session and usage metrics, and the current session preview before the existing action rows. Wire a Diagnostics action into the tray menu and keep the summary computation in a testable, render-free builder shared by existing usage/session rows. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove message previews from tray glance Keep the tray dashboard focused on health, freshness, session metadata, and usage while avoiding conversation message text in the top-level flyout. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Avoid activity snippets in tray dashboard glance Keep the top-level tray dashboard limited to stable metadata so command, query, path, or URL activity text stays out of the glance. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Scott Hanselman <scott@hanselman.com>
Move notifications from the footer rail into a title-bar bell flyout and replace the compact connection status dots with a status pill plus detail flyout. Keep the status presentation aligned with the Connection page by deriving node state from shared presenter logic and the node-mode/capability settings. Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Port PR openclaw#562 onto current main and harden diagnostics redaction so JSONL remains parseable while sensitive values, metadata keys, paths, emails, headers, and compound JSON values are sanitized before export. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Migrate chat and Connection presentation to WinUI theme resources, preserve system-colored Hub navigation in High Contrast, and add runtime coverage for isolated tool and compaction styles. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use stable native anchoring for streamed chat growth and validate immutable queued tail requests against current identity, bounds, item count, and generation before native navigation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* feat(sessions): consume flattened gateway classification * test(sessions): fix flattened facts test syntax * fix(sessions): preserve legacy display fallback * fix(sessions): reject raw key display names Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: bakudies@microsoft.com <bakudies@microsoft.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* docs: clean up README to match main repo polish - Remove outdated screenshots (will add fresh ones separately) - Add badge row (CI, .NET, license, Discord) - Condense intro to one punchy sentence - Add quick-links row and install table - Replace verbose feature lists with concise grid - Move Mac parity table, Quick Send troubleshooting, node capabilities reference, deep links table, and Command Center diagnostics to linked docs - Keep essential dev workflow: setup, build, run, test - Add Documentation links table - Consistent structure: Install > Features > Projects > Dev > Config > Docs > License Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 049e0978-76d3-4105-82c7-898a2fd7f4fd * docs: tighten README based on critical review - Title: 'OpenClaw Windows Hub' (matches product name) - Intro: one sentence what-it-is, one what-it-does (no feature-list sentence) - Install: mention 'Set up locally' happy path - Step 3: concrete CLI example instead of vague config reference - 'Also included' -> 'Features' (proper header) - Docs table: split contributor-facing (table) from user-facing (inline links) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 049e0978-76d3-4105-82c7-898a2fd7f4fd * docs: restore build/run flags that contributors actually need Bring back setup-dev.ps1 flags, build.ps1 -Project, dotnet build with RID (WebView2 gotcha), run-app-local.ps1 -NoBuild/-Dev/-Isolated, and MSIX build command. Organized into Prerequisites/Build/Run/Test subsections instead of one dense block. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 049e0978-76d3-4105-82c7-898a2fd7f4fd * docs: restore user-facing setup detail that people actually need - Node Mode: expand to 4 steps with actual allowCommands JSON config - Add privacy warning (no wildcards, screen.record/camera are opt-in) - Add verify step with test commands from gateway side - Add Deep Links table (key links, not all 30+) - Add File Paths table (settings, logs, gateways, exec-approvals) - Link MCP Mode doc from features list Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 049e0978-76d3-4105-82c7-898a2fd7f4fd * docs: add OpenClaw bot icon to README title Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 049e0978-76d3-4105-82c7-898a2fd7f4fd * docs: make Windows README app-first Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: bakudies@microsoft.com <bakudies@microsoft.com> Copilot-Session: 049e0978-76d3-4105-82c7-898a2fd7f4fd
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* feat: simplify session status presentation * test: preserve session presentation priority * test: pin active session run field * test: align chat threads with session run state * test: seed session status UI fixture * test: add background session to UI fixture * fix: retain session run state * fix: update session fixture for flattened fields * fix: preserve localized resource nodes * fix: align chat activity with run liveness
…claw#1092) * feat(chat): compact consecutive tool activity Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(chat): compact expanded tool rows Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(chat): preserve tool input details Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(chat): space expanded tool details Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(chat): preserve selected tool detail text Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(chat): preserve ordered tool history Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * test(chat): inspect Reactor tool output text Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * test(chat): reconcile tool activity off-tree Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * test(chat): expand compact native tool proof Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(chat): correlate string tool results Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(chat): preserve history tool correlation Prefer semantic tool-result call references and consume aborted assistant state on the first replayed part, including tool-first messages. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
openclaw#1079) Bumps [github/gh-aw-actions/setup-cli](https://github.com/github/gh-aw-actions) from 0.83.3 to 0.84.1. - [Release notes](https://github.com/github/gh-aw-actions/releases) - [Changelog](https://github.com/github/gh-aw-actions/blob/main/CHANGELOG.md) - [Commits](github/gh-aw-actions@6f8e8ef...0292041) --- updated-dependencies: - dependency-name: github/gh-aw-actions/setup-cli dependency-version: 0.84.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/stale](https://github.com/actions/stale) from 10 to 11. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](actions/stale@v10...v11) --- updated-dependencies: - dependency-name: actions/stale dependency-version: '11' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 27dc9d2f-ee94-48f1-bf06-8f3d10414fc9
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 27dc9d2f-ee94-48f1-bf06-8f3d10414fc9
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 27dc9d2f-ee94-48f1-bf06-8f3d10414fc9
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 27dc9d2f-ee94-48f1-bf06-8f3d10414fc9
Preserve native tool identity and safe output convergence while correctly handling successful aborts, failed-abort recovery, and prior-turn legacy replay correlations.
) * fix: prove relayless managed gateway ownership Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix: close relayless provenance races Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * test(e2e): lease mirrored-WSL-safe gateway ports Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 184fa070-4567-45dc-ac2d-c81c2840af14 * test(e2e): prove selected gateway port Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 184fa070-4567-45dc-ac2d-c81c2840af14 * test(e2e): bound Windows port inspection Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 184fa070-4567-45dc-ac2d-c81c2840af14 * test(e2e): harden WSL port proof Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 184fa070-4567-45dc-ac2d-c81c2840af14 * test(e2e): parse localized port exclusions Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 184fa070-4567-45dc-ac2d-c81c2840af14 * test(e2e): tolerate unrelated port exclusions Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Fix gateway reload mode default Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(setup): recover gateway 2026.7.1 restart Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 15b5584f-677b-453f-8aa4-4f33fe60bc50 * test(shared): cover gateway 1012 close frame Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 15b5584f-677b-453f-8aa4-4f33fe60bc50 * test(shared): target managed 1012 loopback Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 15b5584f-677b-453f-8aa4-4f33fe60bc50 * test(shared): verify restart clears handshake Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 15b5584f-677b-453f-8aa4-4f33fe60bc50 * test(shared): place restart handshake setup Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 15b5584f-677b-453f-8aa4-4f33fe60bc50 * fix(setup): harden gateway restart recovery Classify the Gateway 2026.7.1 terminal restart from stable model-check context, and bound managed-local NoListener retries without weakening endpoint ownership checks. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(setup): bound gateway startup races Keep retryable startup close 1013 and typed listener snapshot changes inside the existing managed-local restart window while preserving immediate failure for every other unknown owner. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(connection): retry bootstrap cleanup after save failure Keep gateway registry memory aligned with persisted state when bootstrap credential cleanup cannot be saved, so later connection lifecycle events can retry safely. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 73908040-d4b7-493b-b024-b0c9ebcfc98d --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 184fa070-4567-45dc-ac2d-c81c2840af14 Copilot-Session: 15b5584f-677b-453f-8aa4-4f33fe60bc50 Copilot-Session: 73908040-d4b7-493b-b024-b0c9ebcfc98d
* fix: align exec approval snapshot schema Omit the redundant baseHash response field so file-backed V2 snapshots match the gateway protocol. Keep hash as the compare-and-swap value supplied to system.execApprovals.set. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix: bind reusable Windows exec commands Bind simple canonical CMD carriers to one resolved executable identity for allowlist matching, persistence, metadata, and direct execution. Keep shell syntax and ambiguous command forms on the one-time approval path. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix: harden reusable exec host checks Keep argument-selected Windows code hosts on the one-time approval path, document the maintained host catalog, and enforce the reusable-command prerequisite for durable fallback execution. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix: correct carrier recognition and tighten reusable exec binding Review follow-ups for the reusable exec command binder. Carrier shape (blocker): the binder only accepted a cmd carrier with exactly 5 argv elements, but the gateway sends the command tail already tokenized (upstream fixture: cmd.exe /d /s /c echo "SAFE&&whoami"), so real invocations never bound and allowlists stayed broken. Carrier recognition now lives in a single owner, CanonicalCmdCarrier, which accepts multi-element tails and identifies cmd by basename so the absolute-path form works. MxcConfigBuilder now shares that owner instead of keeping its own divergent copy, which also removes a latent NotSupportedException on carriers with more than 5 elements. Bindable executables (high): PATHEXT resolution can return .vbs, .js, .msc, .com and similar, so a denylist of .bat/.cmd was insufficient for durable rules. Replaced with an .exe-only allowlist, applied in both the binder and the coordinator. Prompt resolved path (high): ExecApprovalEvaluation.Resolution is derived from the allowlist resolutions, which are now empty for unbindable commands, so the approval dialog lost the resolved path on exactly the riskiest path. Restored a fallback to the carrier resolution. Indirect command hosts: corrected the inaccurate claim that this catalog matches the macOS binding model (macOS binds an argPattern instead) and expanded it with 20 argument-selected code hosts. Also documents why baseHash must not be reintroduced into the exec approvals snapshot, records the new ownership in the architecture ledger with guard tests, and documents the interpreter-rule behavior change. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93 * fix: only look through a trusted system cmd carrier Rubber-duck review caught an approval/execution identity mismatch that the previous commit introduced. Unifying carrier recognition on a basename match let any image named cmd.exe be looked through, including a copy in a writable directory. For "C:\Writable\cmd.exe /d /s /c hostname.exe" the binder would bind against the inner hostname.exe, so the approval dialog showed that trusted path, but an allow-once still launches the outer image, which is free to ignore its arguments and run something else entirely. The two owners need different predicates. Serialization must stay permissive: cmd parses its own raw command line rather than going through CommandLineToArgvW, so anything cmd receives has to use the cmd-aware serializer regardless of where the image lives, and being permissive there only ever yields correct quoting. Trust must be strict, because looking through a carrier is what makes the shown identity differ from the executed image. IsCmdExecutable stays as the serialization predicate and keeps its MxcConfigBuilder caller. IsTrustedCarrierExecutable is new and accepts only the bare gateway name or a fully-qualified path in the Windows system directories; the binder now uses TryGetTrustedCanonicalPayload. An unrecognized carrier falls through to the existing indirect-host rejection, so it stays unbindable instead of binding to the wrong identity. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93 * fix: verify resolved cmd carrier identity Require durable binding to resolve the outer carrier to system cmd.exe, restore native .com executables, preserve .com command-host classification, and clarify the live gateway carrier shape. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(exec-approvals): bind durable approvals to arguments, not executable names Durable approval identity is now executable path plus an argument pattern, matching the gateway protocol and the macOS node. Approving one invocation of a program no longer authorizes every other invocation of it. Upstream ground truth is openclaw/openclaw at db90dff1396fecbf7029e9e9ea19d6c6ca3e644e: packages/gateway-protocol/src/schema/exec-approvals.ts, src/infra/exec-approvals-allowlist.ts, src/infra/exec-command-resolution.ts, src/infra/system-run-command.ts. Argument binding (D1) ExecArgPattern owns the wire format. A generated pattern is an anchored regex over the NUL-separated argument subject, matching buildArgPatternFromArgv on Windows. The macOS hashed "sha256:argv:" form is recognized so an entry created there matches here. Patterns are remote-influenced input, so matching is time bounded and a malformed pattern fails closed. ExecAllowlistMatcher now mirrors matchAllowlist: an entry with an argument pattern must match the arguments and wins over a path-only match; a generated entry with no argument pattern never matches, because such an entry predates argument binding and would authorize argv the operator never saw. The name-keyed interpreter catalog in ExecCommandToken is deleted. A name list is not a boundary: renaming an image defeats it and the list can never be complete. Argument binding constrains a renamed interpreter exactly as tightly. rawCommand consistency (D2) ValidatedRunRequest carries rawCommand and the validator rejects a mismatch against command, mirroring the gateway's RAW_COMMAND_MISMATCH. Both accepted forms are honored: the formatted argv and the inline cmd payload. rawCommand is never promoted to executable input. Network executables (D3) A durable rule names a path, not the bytes at that path. UNC and mapped network paths are refused for durable binding because whoever controls the share controls what a past approval authorizes later. Carrier diagnostics (D4) An unbindable carrier now reports why (non-canonical-cmd-carrier, carrier-payload-not-static, untrusted-cmd-carrier-image, and so on) instead of silently producing an allowlist that appears to be ignored. Identity separated from transport (D5) A strictly recognized canonical cmd carrier is authorized by the executable it actually runs, but is executed exactly as received. Substituting the bound direct argv would drop the carrier's in-band PATH and TEMP setup, which is the only environment contract the sandbox currently accepts. An invariant asserts the executed transport still reconstructs the approved request. Images merely named cmd.exe are refused: looking through one would describe the trusted inner program while running the untrusted outer image. Also fixes a real widening bug: rewriting the approvals file dropped source and argPattern, silently converting a narrow generated rule into a path-only one. Behavior change worth calling out: a hand-written path-only rule such as **/wsl.exe now authorizes that executable instead of hard-failing with persistent-approval-not-permitted-for-command-host. That matches upstream and is the operator's explicit choice. Generated rules still pin their arguments. Validation: build.ps1, Shared 3502, Tray 2116, WinNode.Cli 126. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93 * fix(exec-approvals): execute trusted carriers via resolved System32 path The D5 carrier-preservation path kept the request's original argv[0], which is typically a bare `cmd.exe`. LocalCommandRunner.ValidateDirectExecutable requires a fully-qualified executable, so every preserved carrier failed at launch, and a relative argv[0] would also let Windows re-resolve the image against PATH/cwd at spawn time - the exact hijack the resolved-path rule exists to prevent. CanonicalCmdCarrier.ResolveTrustedCarrierPath now resolves a trusted carrier token to the absolute System32/SysWOW64 cmd.exe, and the binder pins executionArgv[0] to it. The anti-drift invariant in CarrierTransportMatchesRequest permits exactly that one substitution (argv[0], compared OrdinalIgnoreCase); argv[1..] must still be Ordinal-identical, so no metacharacter drift can be introduced. A carrier that cannot be resolved to a trusted image is rejected with UntrustedCarrierImage. E2E: adds a multi-element-tail allowlist proof matching the gateway's real wire shape, plus a child-spawn diagnostic. Both approval proofs now assert the approval decision unconditionally and tolerate only STATUS_DLL_INIT_FAILED for the exit code, since some hosts cannot spawn any child executable under MXC. Any other nonzero exit still fails. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93 * docs+test: document argPattern identity model and prove CAS stale-hash rejection WINDOWS_NODE_ARCHITECTURE still described the removed indirect-command-host catalog as the durable security boundary. Replaced with the implemented model: durable identity is executable + argPattern, the platform-specific argPattern wire form, upstream matchAllowlist authorization consequences, the trusted-carrier identity/transport split and its two anti-drift invariants, and the deliberate path-only policy loosening. Adds an architecture ledger row for exec-carrier-transport-identity so a future change cannot quietly substitute the bound direct argv for the carrier while MXC still needs the in-band PATH/TEMP bootstrap. Adds SystemExecApprovals_SetRejectsStaleBaseHash over the real MCP HTTP server: the GET snapshot carries no baseHash, a stale baseHash on .set is rejected, and the rejected write does not mutate the file. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93 * fix: close three authorization holes in exec approval binding Three ways a durable approval could authorize one thing and permit another. Current-directory hijack. The trusted cmd carrier is executed verbatim, so cmd.exe re-resolves the payload executable at launch and it searches the current directory before PATH. Our resolver searches PATH only. A file named like the payload sitting in the working directory therefore meant the binder authorized the PATH copy while cmd.exe launched the local one, and a replay re-resolved to the same PATH copy so no prompt appeared. The binder now refuses to bind when the current directory shadows a bare payload name, so the two resolvers can never disagree. Explicitly relative payloads are unaffected: both resolve them the same way. Remote binding strip. A remote .set was allowed to retain any entry whose pattern matched an existing one. Keeping the path while dropping argPattern and source converted a narrow generated rule into a path-only grant for that executable, which is privilege escalation without adding an entry. Retention now compares the whole authorization identity. NUL in arguments. NUL separates arguments inside a persisted argPattern, so "a\0b" rendered identically to the two arguments "a","b" and a stored rule could match a differently segmented argv. NUL is not representable in a Windows command line anyway, so arguments containing one are rejected before anything is bound or written. Also stamps allowlist usage against the matched entry id and argument binding rather than the pattern alone, so a path-only rule no longer absorbs the usage metadata of a bound sibling, and logs the breadth of each grant. Validation: build.ps1; Shared 3508 passed; Tray 2116 passed; WinNode.Cli 126 passed; MCP exec-approvals integration 4 passed against a live tray subprocess. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93 * fix: harden the exec approval authorization checks against bypass Follow-up review found three ways around the checks added in the previous commit. Identity forgery. The retained-entry identity joined pattern, argPattern and source with NUL, but argPattern legitimately contains NUL. A caller could shift the delimiter between fields to forge a match while leaving a broader, unanchored regex behind: (argPattern "^--version\0$", source "allow-always") had the same joined form as (argPattern "^--version", source "$\0allow-always"). The fields are now compared structurally, so no redistribution can collide. Omitted working directory. The current-directory shadow check treated a null cwd as "no current directory", but a child process inherits this one's and cmd.exe searches it before PATH just the same. It now falls back to the inherited directory instead of skipping the check. Carrier payload NUL. The NUL rejection sat in the direct-binding path only. The carrier tokenizes a payload string into a fresh argv, so a NUL carried inside the payload was checked but the outer command was not. The check now runs at entry, before any carrier parsing, and again after tokenization. Validation: build.ps1 (WinNodeCli hit the known transient GitVersion mutex error; rebuilt clean on retry); Shared 3510 passed; Tray 2116 passed; WinNode.Cli 126 passed; MCP exec-approvals integration 5 passed against a live tray subprocess rebuilt from this head. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93 * docs: record the carrier payload resolution rule and its residual risk Documents the working-directory ambiguity refusal, the NUL argument rule, and the structural retained-entry identity comparison. Also states plainly that the ambiguity check is an approval-time snapshot, so a writable working directory remains in scope for payload substitution until the transport question is settled. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93 * fix: hash retained-entry identities to keep the remote set check bounded The structural comparison introduced in the previous commit replaced a hash set with a linear scan. The submitted allowlist is remote-controlled, so a caller could pass the retention constraint with many duplicate entries and force a full scan of the local allowlist for each one. Restores hashed lookup with a structural tuple comparer, so collision safety does not cost bounded lookup. Source-only commit. Not validated locally: coordinator holds the host. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93 * fix: quarantine legacy command-host rules and pin the trusted cmd carrier D6, targeted quarantine. An allowlist entry with neither `source` nor `argPattern` stays a valid path-only rule for an ordinary executable, but goes inert when its resolved target is a wrapper, interpreter, or code host the previous model refused to approve durably (python, cmd, powershell, pwsh, wsl and the rest of the pre-existing catalog, including versioned interpreters). Those commands prompt instead of matching. The entry is not deleted and is not auto-migrated on first observation. Only an explicit Allow always writes a source-marked, argument-bound sibling, which then matches normally. This keeps the move from a name catalog to argument binding from silently loosening a case that used to be denied outright. The catalog is a legacy-compatibility predicate, not the security boundary; structural wrapper classification plus argument binding remains the model. D7, constrained Option A. The recognized canonical trusted cmd carrier is still executed (its in-band PATH and TEMP bootstrap is required under MXC 0.7, which rejects a non-empty process.env), but it is reconstructed with the payload's executable token pinned to the binder-resolved absolute path, in addition to the already-pinned argv[0]. This closes the working-directory substitution race: without the pin, the binder authorizes using a PATH-only resolver while cmd resolves the same token at launch searching the working directory first, so anything able to write there between approval and launch picked the winner. New `CmdPayloadTokenizer` is the single owner of payload parsing and exposes token spans, so the rewrite is built from the parsed structure and never from a string replacement. An argument that repeats the executable's text survives untouched. Pinning is refused rather than approximated: the pinned path must be readable back by cmd byte for byte, so whitespace, quotes, control characters, `% ! ^ & | < > ( )`, the command-name delimiters `, ; =`, and a trailing backslash all fail closed to prompt-only. Whitespace is refused rather than quoted because under `/s` cmd strips the payload's first and last quote and uses the remainder verbatim, which would reintroduce the ambiguity the pin exists to remove. Reconstruction is validated by round trip: same argument count, the executable equal to the pinned path, every other argument ordinal-identical, tail arity preserved, and the whole carrier still recognized as canonical. The same equivalence is re-checked at execution time. The approval-time working-directory ambiguity check is demoted to a diagnostic and the residual race is removed from the docs. Reconciles remote contributor head b9be5f4: - Adopted: `.com` is a directly-executable image, so it is bindable and is stripped by NormalizedBasename (so powershell.com classifies like powershell.exe). - Adopted: the doc clarification that the live gateway sends one pre-joined tail element and a tokenized tail is a low-level/fixture shape. - Superseded: `IsTrustedSystemCmdPath` detected a bare cmd.exe resolving outside System32 and refused to bind. Pinning is strictly stronger: argv[0] is set to the system image, so a rogue cmd.exe first on PATH is not merely detected, it is never what runs. The weaker predicate must not be resurrected. The test that protected the invariant is preserved in strengthened form. NOT VALIDATED. No build, test, or tool run was performed for this commit: the coordinator holds an exclusive local host lock. Source only, pending the `1109 LOCAL WINDOW START` window. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93 * test: harden pinned-carrier coverage and reconcile .com classification Adds focused CanonicalCmdCarrier coverage for the D7 reconstruction: the request's own switch spelling is preserved, a relative carrier or payload is refused, the pinned carrier matches the request it was built from, any other difference (different program, different argument, injected metacharacter, unqualified payload, changed switch) is refused at execution time, and the PATHEXT extension a bare name gains when resolved is accepted. Refuses pinning a path containing cmd's command-name delimiters , ; = which our tokenizer deliberately does not model, so such a path cannot be split by cmd into a different program plus an argument. Adds the three cases to the representation-safety theory. Avoids nullable/variance-sensitive assertion overloads and collection expressions in generic Assert.Equal calls, since tests build with TreatWarningsAsErrors. NOT VALIDATED. Source only, local host hold still in force. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93 * fix: keep durable binding .exe-only and verify pinned payloads by reconstruction Two coordinator-required corrections to the D6/D7 work. All other D6/D7 behavior is unchanged. 1. Revert the .com durable bindability expansion. b9be5f4 proposed treating .com as bindable alongside .exe, and that was adopted in 4f8252c. It is reverted here. Widening the durable authorization allowlist to another executable format is a separate decision with its own review, and it is out of scope for a change about carrier binding. IsBindableExecutable is .exe only again, and .com rejoins the non-bindable PATHEXT set. NormalizedBasename still strips .com, for one narrow reason that is now documented at the call site: IsLegacyQuarantinedHost has to recognize a provenance-less legacy entry naming powershell.com exactly as it recognizes powershell.exe, otherwise the D6 change in model would silently upgrade a case the old catalog refused outright. That is quarantine classification, not bindability. Both halves now have focused tests (NativeComExtensionTarget_DoesNotBind for the contract, ComExtension_QuarantinesAsTheSameCommandHostAsExe and LegacyPathOnlyEntryForACommandHostWithComExtension_IsInert for the transitional behavior). 2. Verify the pinned payload by reconstruction, not token comparison. PinnedCarrierMatchesRequest documented that all interior spacing must be byte-identical, but it only compared token values. A payload whose tokens matched while its spacing was rewritten passed. It now reconstructs the one payload the request is permitted to have become, by applying the executable token span edit to the request's own payload, and requires ordinal equality with the payload actually being executed. That single check subsumes argument values, argument count, and every byte of interior spacing. PinnedCarrier_RejectsInteriorSpacingChangesThatPreserveTokens covers a doubled space, a tab, a leading space, and a trailing space. Each asserts the tokens are still identical before asserting the match fails, so the rejection is attributable to spacing alone. Docs and the architecture ledger are updated to say .exe only. No local validation, rebase, push, or PR mutation was performed. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93 * fix(exec-approvals): restore the full pre-binding catalog for D6 quarantine D6 asks a purely historical question about an allowlist entry that is already on disk and carries neither provenance nor an argument binding: would this exact entry have been refused durable approval when it was written? The answer has to come from the catalog as it actually stood immediately before argument binding replaced it, not from a fresh judgement about which programs look dangerous today. The previous revision restored that catalog from cdcb74f, which predates the indirect-host expansion, so it silently dropped 28 names: mshta, regsvr32, rundll32, msbuild, csc, vbc, dnx, rcsi, installutil, regasm, regsvcs, mavinject, msiexec, certutil, bitsadmin, wmic, forfiles, scriptrunner, pcalua, cmstp, odbcconf, msdt, ieexec, presentationhost, winrs, hh, msxsl and xwizard. Every one of those was refused durable approval in the shipped release, so a provenance-less entry naming one could only have been written under a model that denied it. Leaving them out would have converted a denial into an unconditional allow on upgrade, which is exactly the regression D6 exists to prevent. The catalog and IsVersionedInterpreter are now a verbatim copy of e4ff61e, the last revision before binding replaced them (69 names, byte-identical and in the same order). The accompanying comment states that this list must not be curated, pruned or extended, because anything added would claim to have been denied in a past release when it was not. This does not reopen D4. The catalog is not the security boundary and cannot become one: IsLegacyQuarantinedHost has exactly one call site (ExecAllowlistMatcher.MatchInternal), reached only when an entry has no ArgPattern and no Source. Every rule this node generates carries an argument binding, so new rules never take that path. WindowsCodeHost_BindsOnlyToThe ApprovedPayload still binds mshta, regsvr32 and rundll32 durably through argument binding, unchanged. Tests: - LegacyPathOnlyEntryForACommandHost_IsInert gains mshta, rundll32, regsvr32, msbuild, certutil and wmic. - LegacyQuarantineCoversTheFullPreBindingCatalog enumerates all 69 names and reports any that were dropped by name, so this class of truncation fails loudly rather than silently widening an allow. - LegacyQuarantineCoversVersionedInterpreters covers python3, python3.12, pythonw3.11 and pypy3.10. - LegacyQuarantineDoesNotCoverLookalikeNames pins that pythonish, python-wrapper and pypycache stay ordinary executables. - LegacyPathOnlyEntryForAnOrdinaryExecutable_StillMatches is the unchanged control for hostname.exe, git.exe and rg.exe. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93 * fix(exec-approvals): keep NormalizedBasename .exe-only and drop the .com quarantine claim Exact archaeology: e4ff61e's NormalizedBasename stripped `.exe` and nothing else. A `.com` spelling therefore never normalized to a catalog name, was never classified as a command host, and was never refused durable approval. The previous revision of this branch taught NormalizedBasename to strip `.com` as well so that a provenance-less entry naming `powershell.com` would go inert. That inverted the very rule D6 exists to enforce: it invents a denial that never happened, which is the same class of error as dropping one that did. It was also far wider than intended. NormalizedBasename is shared by IsEnv, the shell-wrapper normalizer, and the PowerShell and builtin classifiers, so the extra strip silently changed what all of them recognize, well outside the transitional quarantine it was meant to serve. A separate `.com` stripper for the quarantine alone would have been no better, because the historical behavior it must reproduce had no such concept at all. NormalizedBasename is now byte-identical to e4ff61e (verified mechanically), and a legacy entry naming `python.com` keeps working exactly as it did before. Unchanged and re-verified: - IsBindableExecutable is still `.exe` only, so a real Windows `.com` image is still refused durable binding. Bindability and legacy classification are independent questions and stay that way. - The pre-binding catalog is still the complete verbatim 69-name copy from e4ff61e, with the exact versioned-interpreter logic. - PinnedCarrierMatchesRequest still verifies by reconstruction, so interior spacing drift is still rejected. Tests: - ComExtension_QuarantinesAsTheSameCommandHostAsExe becomes ComExtension_IsNotLegacyQuarantined, asserting the opposite for the same three tokens. - LegacyPathOnlyEntryForACommandHostWithComExtension_IsInert becomes ..._StillMatches. - NativeComExtensionTarget_DoesNotBind is untouched: it is the bindability contract and is unaffected by classification. Docs: the WINDOWS_NODE_ARCHITECTURE quarantine section drops the `.com` spelling claim, names the indirect execution hosts now in the catalog, and states the `.exe`-only matching rule with its rationale. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93 * docs: drop the .com discussion from the legacy quarantine section The quarantine section explained at length why a `.com` spelling is not quarantined. The explanation was correct but it kept `.com` in front of the reader in a section about which names were historically refused, which is exactly the confusion to avoid. The rule is stated positively instead: the quarantine compares a basename with only a `.exe` suffix stripped, exactly as the original catalog did. Versioned interpreters (`python3.12`) and the indirect execution hosts are retained in the refused-outright list, since those were genuinely in the historical catalog. No behavior change. The `.com` reference remaining in this document is the pre-existing non-executable extension list, which is about resolution rather than quarantine. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93 * refactor: delete the superseded working-directory ambiguity helper ExecCommandResolver.HasCurrentDirectoryCandidate had zero callers once a trusted carrier's payload executable token became pinned to its resolved absolute path. Keeping it compiled "as a diagnostic" was the wrong disposition for a routine that used to be an authorization boundary: a dead security check is an invitation to wire it back up, and its own doc comment had to argue against that. Deleting it removes the invitation. The check was never sound as a boundary. It could only observe the working directory as it stood when approval was granted, and anything able to write there before launch simply wins after the check has passed. Pinning closes the window properly: cmd performs no second resolution, so there is nothing left to shadow. TryGetProcessCurrentDirectory went with it; it had no other caller. A comment in its place records what was removed and why, so the next reader does not rediscover the idea and reintroduce it. Docs: - ARCHITECTURE ledger row `exec-carrier-cwd-ambiguity-check` no longer carries the helper as still-allowed, and its closure note is discharged. The invariant now states that ExecCommandResolver exposes no such method, so the ledger asserts the absence rather than tolerating the presence. - WINDOWS_NODE_ARCHITECTURE no longer says the check is retained as a diagnostic. No new test: no existing test referenced the helper, so there is no contract to preserve or update. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93 * test: make carrier binding tests independent of the developer's PATH Fourteen carrier tests failed on a machine with coreutils installed, and the binder was right every time. These tests bind a bare `hostname.exe` payload, which resolves through PATH. The assumption was that PATH yields `C:\Windows\System32\hostname.exe`. With coreutils installed it yields `C:\Program Files\coreutils\bin\hostname.exe` instead, and that path contains a space. Under `cmd /s` the first and last quote of the payload are stripped and the rest is used verbatim, so a spaced path cannot be pinned without changing what cmd executes. The binder refuses and returns CarrierPayloadNotPinnable, which is the accepted D7 fail-closed behavior. The tests were asserting on ambient machine state, not on the code. Confirmed by probe against the real binder: Resolve(hostname.exe).ResolvedPath: [C:\Program Files\coreutils\bin\hostname.exe] TryBuildPinnedCarrier: False TryBind -> bound=<null> failure=CarrierPayloadNotPinnable Two fixes, chosen by what each layer can express: Binder-level tests pin PATH to the system directory through the existing `env` argument, so a bare name still has to be resolved through PATH and the behavior under test is unchanged. New ExecTestPath helper holds that, with the reasoning, so the next person does not reintroduce a process-PATH dependency. BareCmdResolvingOutsideSystemDirectory_ExecutesTheSystemImage keeps its rogue directory first but now also has the system directory on PATH, since otherwise the payload cannot resolve at all and the test fails before reaching its claim. Coordinator-level tests cannot do that. ExecApprovalV2InputValidator denies a non-empty env with custom-env-not-supported, so a request cannot carry a PATH and resolution always uses the process PATH. Those three tests name the payload absolutely instead. They are about the coordinator's authorize-and-execute wiring, which an absolute payload exercises identically, and bare-name resolution stays covered at the binder level where env can be injected. This also means the spaced-path refusal now has real-world evidence rather than only a synthetic test: an ordinary developer machine produced exactly the case D7 fails closed on. Validation on this commit: Shared 3618 total, 3586 passed, 0 failed, 32 skipped Tray 2165 total, 2165 passed, 0 failed WinNode 126 total, 126 passed, 0 failed Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93 * fix(exec-approvals): close two adversarial-review gaps in carrier pinning and currency Adversarial review of the exec-approval binding work surfaced two real defects that focused tests now cover. 1. Versioned executables could bind and then hard-fail at execution. PinnedCarrierMatchesRequest required the request name to have no extension before allowing the pinned name to carry one. But Path.HasExtension("python3.11") is true (it sees ".11"), so a versioned tool bound successfully at authorization time and then failed at execution time with InternalError(unresolved-executable-on-allow). The rule is now simply: the pinned name equals the request name, or the request name plus exactly one appended extension. Stem identity is still required, so a pinned name with a different stem is rejected. 2. The mid-approval policy guard could miss a tightened grant. ExecApprovalsCurrency fingerprinted allowlist entries by Pattern only, so adding an argPattern, narrowing an existing argPattern, or marking a path-only entry as generated was invisible to IsStillCurrent. The fingerprint is now the (Pattern, ArgPattern, Source) triple. Pattern keeps case-insensitive comparison for Windows paths while ArgPattern and Source compare ordinally, using a comparer rather than case folding so the ToUpperInvariant/ToLowerInvariant round-trip pitfall cannot collapse distinct identities. Neither change relaxes an authorization decision: both make a previously silent acceptance fail closed or bind correctly. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93 * fix(exec-approvals): fail closed on any provenance marker; make MXC proof hermetic Second adversarial reviewer findings. Accepted: source gating in ExecAllowlistMatcher was fail-open. A path-only entry was treated as generated (and therefore inert) only when its source was exactly "allow-always" under an ordinal comparison. Any other non-empty value - a different case, a padded string, a marker written by another producer, or a corrupted one - fell through to the path-only branch. That was fail-open twice over: the entry gained path-only reach, and because its source was non-empty it also skipped the legacy quarantine check. The rule is now simply that any non-empty source means a generator produced the entry, so a generated entry that lost its argPattern is inert regardless of spelling. Provenance is absent only when source is empty or whitespace, and those entries keep hand-written path-only semantics behind the quarantine check exactly as before. No entry that used to be denied becomes allowed. Rejected: persisted command text as a secret-disclosure regression. The reviewer proposed redacting CommandText/LastUsedCommand and filtering them out of system.execApprovals.get. That does not mitigate the stated risk and would break two contracts. ArgPattern already encodes the exact argv and is load-bearing for authorization, so arguments are on disk either way; redaction would only stop an operator from auditing what they approved. The get payload is returned verbatim and its hash is the concurrency token for a matching set, so filtering the bytes would break read-modify-write. Returning the file from execApprovals.get also predates this branch. The invariant is recorded on the contract instead. Also makes the MXC exec-approval proof hermetic. It used hostname.exe, which is commonly shadowed on a developer PATH by a copy in a directory the sandbox does not grant, so the proof could fail for reasons that have nothing to do with approvals. It now uses whoami.exe, which has no common shadow. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93 * test(mxc-e2e): recognize the second host child-spawn incapacity signature The MXC exec-approval proofs already tolerated one shape of "this host cannot spawn a child executable inside the AppContainer": exit code 0xC0000142, STATUS_DLL_INIT_FAILED. The same incapacity also appears as CreateProcess being refused outright, which cmd reports as exit code 1 with "Access is denied." on stderr. On a host expressing the second shape the proofs failed as if approvals were broken, which is the wrong report. The evidence that this is a host property and not an approvals or binding problem is the child-spawn diagnostic, which runs with grant=none. No allowlist entry participates, nothing is bound, and the carrier executes exactly as the gateway sent it, with a bare executable name and no pinned path. It still fails, while the echo builtin in the same payload and the same container succeeds. It also reproduces under security=full. So it is not caused by argument binding, by pinning a payload to a resolved absolute path, or by any V2 decision. The tolerance is deliberately narrow: exit code 1 together with exactly that stderr message. It does not weaken what these tests prove. Each caller separately asserts the approval decision and the MXC request shape from the tray log, and a real approvals regression surfaces as decision=deny or a node error rather than this signature. Any other nonzero exit code still fails. Documents the limitation and its validation consequences in the gateway node exec FAQ. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93 * test(mxc-e2e): report an incomplete child-spawn probe as host capability, not failure On a host that cannot spawn a child executable inside the MXC AppContainer, the sandbox run does not always come back with a clean nonzero exit. Observed here: wxc-exec starts the container, the node logs decision=allow, and the run then never returns a result at all, so the gateway CLI call gives up and the probe fails before it can read any payload. That surfaced as an approvals proof failing at AssertCommandSucceeded, which blames exec approvals for a sandbox-runtime limitation of the host. The child-spawn diagnostic now reports an incomplete probe as what it is: host capability could not be established. This test asserts no approvals behavior, so nothing is being swallowed. The approval decision and the MXC request shape are asserted by the two bound-rule proofs, which still fail normally, and every other nonzero exit code is still a failure everywhere else. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93 * fix: pin the canonical cmd carrier for one-time allows too Adversarial review found that the two launch-time pins were applied only when execution went through the reusable transport, which was selected for durable allows and match-dependent fallback allows but not for a one-time Allow Once. An Allow Once therefore executed the request's own argv: argv[0] was the request's PATH-resolved cmd.exe, and a bare payload name stayed bare for cmd to resolve again at launch, searching the working directory before PATH. The prompt meanwhile names the inner executable the binder resolved through a trusted system cmd.exe, so the operator approved one image while a different one could run. With a writable directory ahead of the system directory on the node's PATH, approving a command once could execute attacker-controlled code. Approval identity, durability, and execution transport are three separate things. Route a recognized canonical carrier through its pinned transport whenever one exists, independent of durability. Durability is still gated on requiresReusableExecution, so a one-time allow persists nothing. Regression test asserts the pinned payload token; it fails without this change with the token still bare. The test discovers a bindable payload from a candidate list because a coordinator request cannot carry a custom env and a shadowed system name can resolve under a spaced path. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93 * fix: pin execution transport on every approved exec path Follow-up to ce0a3cb from the same adversarial review, which found two more paths that still executed an image other than the one the operator approved. 1. The pre-approved branch chose the reusable transport only for an allowlist hit, so a security=full/ask=off run reached process launch with the carrier unpinned: argv[0] resolved through PATH and a bare payload token stayed bare for cmd to resolve again at launch. Full policy already permits arbitrary execution, so this is not an authorization escalation, but it left the "every approved run is pinned" invariant untrue and the two allow branches inconsistent. 2. A direct command is resolved twice, once by the normalizer for the execution identity and once by the binder for the identity that is displayed and stored. Execution used the normalizer's, so the prompt could name one image while a separate lookup of the same name selected another. Both collapse into one rule, so transport selection now lives in a single helper used by both decision points: whenever the binder produced a reusable command, that command's argv is what runs. This is safe for direct commands because BindDirect already applies the same env-wrapper unwrapping and modifier rejection as BuildApprovedExecution and adds strictly more gates, so only the source of the resolved executable changes. Durability is unchanged and still gated separately, so no allow persists anything it did not persist before. PreApprovedFullPolicy_CanonicalCarrier_ExecutesThePinnedCarrier fails without this change with the payload token still bare. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93 --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: Scott Hanselman <scott@hanselman.com> Copilot-Session: 1223ff2c-11b6-48d9-937b-a56408e99b93
…aw#1113) * fix(chat): preserve image paste after rerender Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(chat): restore reliable composer context menu Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* fix(exec): clarify executable allowlist rules Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(exec): align allowlist copy with runtime policy Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: Scott Hanselman <scott@hanselman.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Trust only the optional Gateway-stamped top-level sessionKey for node invocation attribution while keeping omitted or malformed attribution backward-compatible and untrusted nested values ignored. Preserve the correlated handshake authorization added by openclaw#1076 and cover both invocation transports plus approval planning. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…penclaw#1104) * ci: add Windows node release candidate E2E * ci: test Windows node release artifacts * ci: exclude MXC from hosted release candidate E2E * ci: bind Windows candidate E2E to release provenance * ci: harden release candidate E2E gate Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * ci: fetch full history for release E2E Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * ci: restore WinUI runtime assets for release E2E Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * ci: restore release E2E runtime assets Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * test: assert E2E restore ordering Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * test: bind E2E restore to RID builds Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * test: order E2E restore before its build Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * ci: bind Windows release asset tuple * fix(setup): clean staged package on cancellation * ci: restore exact release candidate validation Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * ci: bind candidate artifact source run Download the exact caller artifact from its immutable workflow run instead of assuming the reusable workflow shares the producer run. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 84bc73e4-939a-4e25-ba88-26af9f4fc8b5 * fix: support gateway command config migration Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 84bc73e4-939a-4e25-ba88-26af9f4fc8b5 * fix(setup): replay normalized gateway restarts Recognize the Gateway 2026.7.2 wizard restart error so setup uses the existing bounded, ownership-verified replay path instead of failing before reconnect. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 84bc73e4-939a-4e25-ba88-26af9f4fc8b5 * fix(setup): own gateway lifecycle during wizard Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 84bc73e4-939a-4e25-ba88-26af9f4fc8b5 --------- Co-authored-by: Scott Hanselman <scott@hanselman.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 84bc73e4-939a-4e25-ba88-26af9f4fc8b5
* fix(setup): retry wizard migration lease restoration Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(setup): use full restoration retry budget Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related to openclaw#894
Scope
(RunId, ToolCallId, LegacyTurn)identities while preserving reused call IDs across distinct runs and legacy turns.history-tool-*identity without propagating unverified positional cachedRunIdorToolCallIdvalues.This is a surgical three-file change based on current
mainat77141d27ee69728da6c3509fb116de63bd176da4.hideFromChannelProgressis intentionally out of scope.Validation
git diff --check.\build.ps1dotnet test .\tests\OpenClaw.Shared.Tests\OpenClaw.Shared.Tests.csproj --no-restore: 3,631 passed, 0 failed, 32 skippeddotnet test .\tests\OpenClaw.Tray.Tests\OpenClaw.Tray.Tests.csproj --no-restore: 2,252 passed, 0 failedNativeToolIdentityScreenshotProofTestsandReactorToolActivityProofTests: 2 passed, 0 failedReal behavior proof
Review
A fresh GPT-5.6 Sol/high independent review found one medium issue: stale persisted
IdentityStrengthvalues were compared before current canonicalization. The implementation now canonicalizes cloned entries before merge, with a regression proving an invalid stale explicit identity cannot suppress a later valid specific identity. A fresh GPT-5.6 Sol/high rubber-duck pass after that fix found no remaining issues and confirmed the scope remains surgical.PR openclaw#1111 interaction
PR openclaw#1111 is complementary and remains unmodified. Its current head is
be50098bae12e6291ad60b2196ca50996837f94e, rebased/merged with base snapshot9f9a8eda6340c7ec6f0acbc410de4f6b7d24a3ce, and it is now marked ready rather than draft. Its semantic patch still owns persisted call ID aliases, message-level errors, string-encoded safe arguments, and failed-tool grouping.The only nearby provider change in openclaw#1111 replaces structured-history argument extraction with
ExtractSafeToolDisplayArgsValueand removes the old converter. This PR changes the separate flattened-history branches and cache combination/read-validation seam. There is no direct changed-line overlap in the source or tests. The changes are complementary: openclaw#1111 improves verified structured payload projection; this PR corrects positional cache correlation and flattened row identity.Release boundary
openclaw#894 must remain open after this PR lands. Closeout requires a release that includes openclaw#1078, openclaw#1114, and this follow-up.