install-ordering guard leaves pre-existing lease hooks armed when stale mosaic lacks hidden guard command #1202

Open
opened 2026-08-13 15:28:50 +00:00 by Ghost · 0 comments

Root cause

mosaic-link-runtime-assets treats command -v mosaic as enough to enter the CLI-guard path. With an installed stale CLI (observed: 0.0.48) that does not implement __link-claude-settings, the command exits non-zero with unknown command and writes no destination. The script then sets guard_degraded=1 and continues; it does not run the Python stripping fallback and does not sanitize the existing ~/.claude/settings.json.

If that existing destination already contains mutator-gate.py / receipt-observer-client.py, those hooks remain armed despite the installer message claiming they were not wired. The framework file sync can meanwhile replace a prior stub client with active fail-closed code. Existing long-lived Claude processes have no MOSAIC_LEASE_* identity, so the next tool/Stop hook immediately bricks them.

Measured live sequence: framework files refreshed at 10:09; installed global CLI was 0.0.48 and rejected __link-claude-settings; existing settings retained both enforcement hooks; two pre-existing sessions then failed. A safe Read payload now returns GATE_UNAVAILABLE; a small valid observer fixture reaches the masked next error, missing MOSAIC_RECEIPT_OBSERVER_SOCKET.

Test gap

test-install-ordering-guard.sh scenario 2 uses a fake CLI that copies the destination and then exits 1. It never covers a stale/unknown-command CLI that exits before writing. Scenario 4 covers only no mosaic on PATH. It also starts from a fresh destination, so it misses dangerous pre-existing hooks.

Required behavior

  • On any guard invocation failure, unless the explicit opt-out was supplied, leave the destination in a verified safe state with only the two lease-enforcement entries stripped and every unrelated/custom hook preserved.
  • Make the write atomic and verify the resulting destination has neither enforcement marker before reporting degraded completion.
  • Distinguish a guard-decision non-zero from failure to execute the guard contract, or run one shared sanitizer for both.
  • Add a regression that pre-seeds a destination containing both enforcement hooks plus unrelated custom hooks, puts a stale fake mosaic on PATH that returns unknown-command without writing, and asserts enforcement is removed while custom hooks remain.
## Root cause `mosaic-link-runtime-assets` treats `command -v mosaic` as enough to enter the CLI-guard path. With an installed stale CLI (observed: 0.0.48) that does not implement `__link-claude-settings`, the command exits non-zero with `unknown command` and writes no destination. The script then sets `guard_degraded=1` and continues; it does not run the Python stripping fallback and does not sanitize the existing `~/.claude/settings.json`. If that existing destination already contains `mutator-gate.py` / `receipt-observer-client.py`, those hooks remain armed despite the installer message claiming they were not wired. The framework file sync can meanwhile replace a prior stub client with active fail-closed code. Existing long-lived Claude processes have no `MOSAIC_LEASE_*` identity, so the next tool/Stop hook immediately bricks them. Measured live sequence: framework files refreshed at 10:09; installed global CLI was 0.0.48 and rejected `__link-claude-settings`; existing settings retained both enforcement hooks; two pre-existing sessions then failed. A safe Read payload now returns `GATE_UNAVAILABLE`; a small valid observer fixture reaches the masked next error, missing `MOSAIC_RECEIPT_OBSERVER_SOCKET`. ## Test gap `test-install-ordering-guard.sh` scenario 2 uses a fake CLI that copies the destination and then exits 1. It never covers a stale/unknown-command CLI that exits before writing. Scenario 4 covers only no `mosaic` on PATH. It also starts from a fresh destination, so it misses dangerous pre-existing hooks. ## Required behavior - On any guard invocation failure, unless the explicit opt-out was supplied, leave the destination in a verified safe state with only the two lease-enforcement entries stripped and every unrelated/custom hook preserved. - Make the write atomic and verify the resulting destination has neither enforcement marker before reporting degraded completion. - Distinguish a guard-decision non-zero from failure to execute the guard contract, or run one shared sanitizer for both. - Add a regression that pre-seeds a destination containing both enforcement hooks plus unrelated custom hooks, puts a stale fake `mosaic` on PATH that returns unknown-command without writing, and asserts enforcement is removed while custom hooks remain.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#1202