fix(lease): constrain read-only tool carve-outs

This commit is contained in:
Jason Woltje
2026-08-07 14:50:19 -05:00
parent e76f293597
commit 3d98c83273
2 changed files with 13 additions and 24 deletions
@@ -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"
@@ -37,22 +37,20 @@ READ_ONLY_TOOLS = daemon.READ_ONLY_TOOLS
# mcp__<server>__<tool>, 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: