fix(subagent): enforce GrantKind::Tool TTL/revocation before dispatch#6625
Merged
Conversation
bug-ops
force-pushed
the
fix/6567-tool-grant-enforcement
branch
from
July 21, 2026 00:03
5fd362a to
a56169d
Compare
bug-ops
enabled auto-merge (squash)
July 21, 2026 00:03
GrantKind::Tool grants were TTL-tracked in PermissionGrants identically to GrantKind::Secret, but no production call site ever checked one before dispatching a tool - a latent trap where the type implied enforcement that did not exist. Share PermissionGrants via Arc<Mutex<>> between SubAgentHandle and the spawned agent-loop task (Secret grants are delivered per-key over a channel; Tool grants need live shared state instead, since revoke_all() must be visible without a delivery round-trip). Add PermissionGrants::check_tool_grant() and enforce it as the first check in handle_tool_step, before any hook fires or the executor runs. A loop-local active_tool_grants_seen set distinguishes "revoked" from "never granted" after sweep_expired() evicts a stale entry - without it, a rejected dispatch would silently permit the very next one for the same tool. Confirmed semantics: default-permit, time-box only, not an allow-list - absence of a grant always permits; a grant for one tool never restricts another; matching is exact-name only.
bug-ops
force-pushed
the
fix/6567-tool-grant-enforcement
branch
from
July 21, 2026 00:09
a56169d to
2568418
Compare
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.
Closes #6567
Summary
GrantKind::Toolcapability grants were TTL-tracked inPermissionGrantsidentically toGrantKind::Secret, but no production call site ever checked one before dispatching a tool - a latent trap where the type and doc comments implied task-scoped enforcement that did not actually exist.PermissionGrantsviaArc<Mutex<>>betweenSubAgentHandleand the spawned agent-loop task (AgentLoopArgs::tool_grants) -Secretgrants are delivered per-key over a channel, butToolgrants need live shared state sorevoke_all()(task completion/cancellation/drop) is visible without a delivery round-trip.PermissionGrants::check_tool_grant()(NoGrant/Active/Expired) and enforce it as the first check inhandle_tool_step, before any hook fires or the executor runs, mirroring theSecretpath's rejection convention.active_tool_grants_seenset distinguishes "revoked/expired" from "never granted" aftersweep_expired()evicts a stale Vec entry. Adversarial review caught two distinct instances of the same "deny-once-then-permit-forever" bypass this closes: (1) the rejection path was removing the name from the seen-set, undoing its own protection; (2) a grant already expired on its very first check never entered the seen-set at all. Both fixed; regression tests dispatch a third time after expiry/revocation to prove continued denial.Toolgrant always permits; a grant for one tool never restricts any other tool; matching is exact-name only (no prefix/glob for tool families). Documented at the enforcement point so a future caller expecting least-privilege semantics isn't misled.GrantKind::Toolgrant yet (explicitly out of scope per the spec, deferred to a future product decision) - the enforcement point itself satisfies the acceptance criteria independent of any grant-creation API.Spec:
.local/specs/083-tool-grant-enforcement-gap/spec.mdTest plan
cargo +nightly fmt --checkcargo clippy --profile ci --workspace --all-targets --features "desktop,ide,server,chat,pdf,scheduler,testing" -- -D warningscargo nextest run --config-file .github/nextest.toml --workspace --features "desktop,ide,server,chat,pdf,scheduler" --lib --bins(15033 passed)cargo test --doc --workspace --features "desktop,ide,server,chat,pdf,scheduler"RUSTFLAGS="-D warnings" RUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links" cargo doc --no-deps --workspace --features "desktop,ide,server,chat,pdf,scheduler"gitleaks protect --staged --no-banner --redact.local/testing/playbooks/subagent-tool-grant-enforcement.md) - no live CLI/TUI/Telegram scenario is possible yet since no production caller creates aToolgrant; coverage is unit-level, documented explicitly rather than skipped