From cd4f72fe17b88224c284c6bcaba72cfe705db4cc Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Mon, 21 Sep 2026 14:42:56 +0200 Subject: [PATCH] fix(agent-isolation): disarm the touch overlay when a command never runs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The documented arm/disarm wiring assumed every armed command eventually executes. `PreToolUse` fires before the permission prompt, so a command the operator rejects has already armed a watcher that `PostToolUse` never tears down. It then lives until `MAX_WAIT` — ten minutes — and because `signing_in_flight` is a machine-wide `pgrep` for `gpg` / `ssh-keygen` rather than something scoped to the armed context, any unrelated signature inside that window raises the window with nothing of the operator's pending. The visible symptom is the one the overlay exists to prevent: a touch landing while no signature waits fires the key's OTP slot and types `cccc…` into whatever has focus. Observed three times in one session. Wires `disarm` onto `PermissionDenied` and `PostToolUseFailure` — the events that fire when a Bash call does not run — in the setup doc, the install skill's K.2, the verify skill's check 10a, and the sandbox spec. `disarm` is already idempotent and exits 0 with nothing armed, so the extra calls cost nothing and no script change is needed. Generated-by: Claude Opus 5 --- docs/mode-economics.md | 6 +++--- docs/setup/secure-agent-setup.md | 18 ++++++++++++++++++ .../skills/isolated-setup-install/SKILL.md | 15 ++++++++++----- .../skills/isolated-setup-verify/SKILL.md | 10 ++++++++-- .../spec-loop/specs/agent-isolation-sandbox.md | 6 +++++- 5 files changed, 44 insertions(+), 11 deletions(-) diff --git a/docs/mode-economics.md b/docs/mode-economics.md index 0c2f982c..6b6e2df3 100644 --- a/docs/mode-economics.md +++ b/docs/mode-economics.md @@ -92,7 +92,7 @@ special-token spellings counted as ordinary text. Coverage: **75 of 75 local `skills/*/SKILL.md` files**. External `source.md` redirects and harness symlinks are excluded. -Measurement manifest SHA-256: `026fe6f701d8b3c1050c01f3a7bc192b8996ae0f5f7702290cab4c5c052806d8`. +Measurement manifest SHA-256: `4556129999018ce339eb0f7c8292e18a424d57716fadd8407f29d9c4a9decec3`. | Skill file | Measured tokens | Source SHA-256 (first 16 characters) | |---|---:|---| @@ -160,9 +160,9 @@ Measurement manifest SHA-256: `026fe6f701d8b3c1050c01f3a7bc192b8996ae0f5f7702290 | [security-tracker-stats-dashboard](../skills/security-tracker-stats-dashboard/SKILL.md) | 4,897 | `b52154deb8557ba4` | | [setup](../skills/setup/SKILL.md) | 8,724 | `82788542bb240309` | | [setup-isolated-setup-doctor](../skills/setup-isolated-setup-doctor/SKILL.md) | 7,950 | `3fa5d728fa080ed0` | -| [setup-isolated-setup-install](../skills/setup-isolated-setup-install/SKILL.md) | 11,197 | `c046c45d2e176827` | +| [setup-isolated-setup-install](../skills/setup-isolated-setup-install/SKILL.md) | 11,278 | `418ea9794077c6e9` | | [setup-isolated-setup-update](../skills/setup-isolated-setup-update/SKILL.md) | 5,069 | `dd56cf376cd14f55` | -| [setup-isolated-setup-verify](../skills/setup-isolated-setup-verify/SKILL.md) | 8,409 | `3ba7f351ebfa32bd` | +| [setup-isolated-setup-verify](../skills/setup-isolated-setup-verify/SKILL.md) | 8,502 | `6f0a2594801bdf27` | | [setup-override-upstream](../skills/setup-override-upstream/SKILL.md) | 4,012 | `fb583feb56b7f77c` | | [setup-privacy-llm](../skills/setup-privacy-llm/SKILL.md) | 2,145 | `0e27b542a1656846` | | [setup-shared-config-sync](../skills/setup-shared-config-sync/SKILL.md) | 4,357 | `d1dfcd7cdeb5f5a6` | diff --git a/docs/setup/secure-agent-setup.md b/docs/setup/secure-agent-setup.md index f696a664..b47eb49d 100644 --- a/docs/setup/secure-agent-setup.md +++ b/docs/setup/secure-agent-setup.md @@ -2182,11 +2182,29 @@ in the `Bash` matcher groups you already have: "hooks": [ { "type": "command", "command": "~/.claude/scripts/gpg-touch-overlay.sh disarm" } ] } + ], + "PermissionDenied": [ + { "matcher": "Bash", + "hooks": [ + { "type": "command", "command": "~/.claude/scripts/gpg-touch-overlay.sh disarm" } + ] } + ], + "PostToolUseFailure": [ + { "matcher": "Bash", + "hooks": [ + { "type": "command", "command": "~/.claude/scripts/gpg-touch-overlay.sh disarm" } + ] } ] } } ``` +**Disarm is wired to three events, not one.** +`PreToolUse` fires *before* the permission prompt, so a command the operator rejects has already armed a watcher that `PostToolUse` will never tear down. +It then lives until `MAX_WAIT` — ten minutes — and because `signing_in_flight` is a machine-wide `pgrep` for `gpg` / `ssh-keygen` rather than something scoped to the armed context, any unrelated signature inside that window raises the window with nothing of the operator's pending. +The touch that follows lands while no signature waits, which fires the key's OTP slot and types `cccc…` into whatever has focus. +`PermissionDenied` and `PostToolUseFailure` are the events that fire when a Bash call does not run; `disarm` is idempotent and exits 0 with nothing armed, so the extra calls cost nothing. + On Linux this needs `python3` with PyGObject for the dimmed overlay; where that is missing it falls back to a `zenity` dialog. On macOS it needs a `python3` with Tk 8.6 or newer — a uv-managed python or diff --git a/plugins/magpie-setup/skills/isolated-setup-install/SKILL.md b/plugins/magpie-setup/skills/isolated-setup-install/SKILL.md index f28e4725..8d98a614 100644 --- a/plugins/magpie-setup/skills/isolated-setup-install/SKILL.md +++ b/plugins/magpie-setup/skills/isolated-setup-install/SKILL.md @@ -654,11 +654,16 @@ key. Leave the attestation slot alone. A slot already at `On` or `tools/agent-isolation/gpg-touch-overlay.sh`, `gpg-touch-overlay-window.py` and `gpg-touch-overlay-window-macos.py` into `~/.claude/scripts/`, `chmod +x` them, and wire a `PreToolUse` -`Bash` hook running `gpg-touch-overlay.sh arm` and a `PostToolUse` -`Bash` hook running `gpg-touch-overlay.sh disarm` into -`~/.claude/settings.json` — merging into existing arrays with a diff -the operator approves, exactly as for the bypass-warn hook. Install -detail: +`Bash` hook running `gpg-touch-overlay.sh arm` and a +`gpg-touch-overlay.sh disarm` hook on **three** events — +`PostToolUse`, `PermissionDenied` and `PostToolUseFailure`, each on +the `Bash` matcher — into `~/.claude/settings.json`, merging into +existing arrays with a diff the operator approves, exactly as for the +bypass-warn hook. The last two matter because `PreToolUse` fires +before the permission prompt: a command the operator rejects has +already armed a watcher that `PostToolUse` never tears down, and it +lives until `MAX_WAIT` raising the window for somebody else's +signature. Install detail: [docs/setup/secure-agent-setup.md → Hardware-key touch overlay](../../../../docs/setup/secure-agent-setup.md#hardware-key-touch-overlay). **K.3 — Git from the operator's own terminal.** The hook covers only diff --git a/plugins/magpie-setup/skills/isolated-setup-verify/SKILL.md b/plugins/magpie-setup/skills/isolated-setup-verify/SKILL.md index 8aacdef8..15b084f8 100644 --- a/plugins/magpie-setup/skills/isolated-setup-verify/SKILL.md +++ b/plugins/magpie-setup/skills/isolated-setup-verify/SKILL.md @@ -348,8 +348,14 @@ Walk each in order: **10a — wiring and scripts.** User-scope `~/.claude/settings.json` has a `PreToolUse` `Bash` hook running - `gpg-touch-overlay.sh arm` and a `PostToolUse` `Bash` hook running - `gpg-touch-overlay.sh disarm`, and the scripts are present and + `gpg-touch-overlay.sh arm` and a `gpg-touch-overlay.sh disarm` + `Bash` hook on each of `PostToolUse`, `PermissionDenied` and + `PostToolUseFailure`. A missing `PostToolUse` disarm is ✗; a + missing `PermissionDenied` or `PostToolUseFailure` disarm is ⚠ + with the reason — the overlay still works, but a rejected or + failed command leaves its watcher armed until `MAX_WAIT`, and any + unrelated signature in that window raises the window with nothing + pending. The scripts must also be present and executable: `~/.claude/scripts/gpg-touch-overlay.sh`, plus `gpg-touch-overlay-window.py` (Linux) or `gpg-touch-overlay-window-macos.py` (macOS) beside it. Compare diff --git a/tools/spec-loop/specs/agent-isolation-sandbox.md b/tools/spec-loop/specs/agent-isolation-sandbox.md index 7aeb7831..8c5835f8 100644 --- a/tools/spec-loop/specs/agent-isolation-sandbox.md +++ b/tools/spec-loop/specs/agent-isolation-sandbox.md @@ -47,7 +47,11 @@ existing sandbox grants can widen the baseline. See `docs/adapters/gemini.md`. signing key or ssh authentication key with a touch policy blocks waiting for a touch. Two entry points: `arm` / `disarm` as a `PreToolUse` / `PostToolUse` `Bash` hook around the - agent's git commands, and `wrap` as git's own signing program and ssh + agent's git commands — with `disarm` also on `PermissionDenied` and + `PostToolUseFailure`, because `PreToolUse` fires before the + permission prompt and a command that never runs would otherwise + leave its watcher armed until `MAX_WAIT`, raising the window for an + unrelated signature — and `wrap` as git's own signing program and ssh command (`gpg.ssh.program` / `gpg.program` through an argument-free `gpg-touch-wrap-` symlink, `core.sshCommand … wrap ssh`) for the commits and pushes the operator makes by hand — no git hook type