From 353296d3cf0f003c9234ab13fbf1b6cee3414e5b Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Fri, 7 Aug 2026 14:50:19 -0500 Subject: [PATCH] fix(lease): constrain read-only tool carve-outs --- .../framework/tools/lease-broker/daemon.py | 4 +-- .../src/lease-broker/invariant_r_unittest.py | 33 +++++++------------ 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/packages/mosaic/framework/tools/lease-broker/daemon.py b/packages/mosaic/framework/tools/lease-broker/daemon.py index 5829a7ef..54fe7424 100644 --- a/packages/mosaic/framework/tools/lease-broker/daemon.py +++ b/packages/mosaic/framework/tools/lease-broker/daemon.py @@ -50,8 +50,8 @@ LEASE_PENDING: Final = "PENDING_VERIFICATION" LEASE_PENDING_PROMOTION: Final = "PENDING_PROMOTION" LEASE_VERIFIED: Final = "VERIFIED" READ_ONLY_TOOLS: Final = { - "claude": frozenset({"Read", "Grep", "Glob", "Ls", "Find"}), - "pi": frozenset({"read", "grep", "find", "ls"}), + "claude": frozenset({"Read", "Grep", "Glob"}), + "pi": frozenset({"read", "ls"}), } RECOVERY_TOOL: Final = "mosaic_context_recover" diff --git a/packages/mosaic/src/lease-broker/invariant_r_unittest.py b/packages/mosaic/src/lease-broker/invariant_r_unittest.py index d260b61d..c673dd2f 100644 --- a/packages/mosaic/src/lease-broker/invariant_r_unittest.py +++ b/packages/mosaic/src/lease-broker/invariant_r_unittest.py @@ -37,22 +37,20 @@ READ_ONLY_TOOLS = daemon.READ_ONLY_TOOLS # mcp____, so they cannot replace these bare identities. CLAUDE_PROVEN_READ_ONLY_TOOLS: Final = frozenset({"Read", "Grep", "Glob"}) -# W-C owns removal of these known-dead names from daemon.py. This pin is -# deliberately two-sided: until W-C lands, each name must remain in the live -# carve-out and absent from the installed/proven set. W-C must delete the pin in -# the same commit that removes the daemon entries, or this suite turns red. -KNOWN_DEAD_CLAUDE: Final = frozenset({"Ls", "Find"}) - # W-B measured Pi 0.84.1 through getAllTools(), observed every tool_call name, -# and cross-checked dist/core/tools/index.js:18. Keep the mutating names here so -# a runtime registry change forces the security classification to be revisited. +# and cross-checked dist/core/tools/index.js:18. Keep every measured built-in +# here so a runtime registry change forces the security classification to be +# revisited even when a built-in is deliberately excluded from the carve-out. PI_VERSION: Final = "0.84.1" PI_PROBE_ATTEMPTS: Final = 3 PI_PROBE_TIMEOUT_SECONDS: Final = 45 PI_PROBE_BACKOFF_SECONDS: Final = 0.25 -PI_PROVEN_READ_ONLY_TOOLS: Final = frozenset({"read", "grep", "find", "ls"}) +PI_PROVEN_READ_ONLY_TOOLS: Final = frozenset({"read", "ls"}) +PI_SUBPROCESS_TOOLS: Final = frozenset({"grep", "find"}) PI_MUTATING_TOOLS: Final = frozenset({"bash", "edit", "write"}) -PI_MEASURED_BUILTINS: Final = PI_PROVEN_READ_ONLY_TOOLS | PI_MUTATING_TOOLS +PI_MEASURED_BUILTINS: Final = ( + PI_PROVEN_READ_ONLY_TOOLS | PI_SUBPROCESS_TOOLS | PI_MUTATING_TOOLS +) # Pi 0.84.1 built-ins individually proven incapable of subprocess execution or # filesystem writes on their default path: @@ -177,25 +175,16 @@ class InvariantRTest(unittest.TestCase): def test_live_carve_out_has_only_supported_runtimes(self) -> None: self.assertEqual(set(READ_ONLY_TOOLS), {"claude", "pi"}) - def test_claude_carve_out_is_registered_proven_and_pinned_for_w_c(self) -> None: + def test_claude_carve_out_is_registered_and_proven(self) -> None: carve_out = set(READ_ONLY_TOOLS["claude"]) falsifier = os.environ.get(CLAUDE_EXTRA_TOOL_ENV) if falsifier: carve_out.add(falsifier) - self.assertLessEqual( - KNOWN_DEAD_CLAUDE, - carve_out, - "W-C changed the live Claude carve-out but left its known-defect pin stale", - ) - self.assertTrue( - KNOWN_DEAD_CLAUDE.isdisjoint(CLAUDE_PROVEN_READ_ONLY_TOOLS), - "a W-C dead-name pin unexpectedly exists in Claude's installed/proven tool set", - ) self.assertEqual( - carve_out - KNOWN_DEAD_CLAUDE, + carve_out, set(CLAUDE_PROVEN_READ_ONLY_TOOLS), - "every unpinned Claude carve-out must exist and be in the exact proven read-only allow-list", + "every Claude carve-out must exist and be in the exact proven read-only allow-list", ) def test_pi_carve_out_has_no_exec_or_write_capability(self) -> None: