Skip to content

Fix project-scoped grants never matching sub-agent worktree requests - #386

Merged
TheGreatAxios merged 1 commit into
mainfrom
cl-5662-project-grants-never-match-subagent-worktree
Aug 8, 2026
Merged

Fix project-scoped grants never matching sub-agent worktree requests#386
TheGreatAxios merged 1 commit into
mainfrom
cl-5662-project-grants-never-match-subagent-worktree

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Collaborator

Summary

  • mintGrant stamps a project grant's cwd with the session root, but a sub-agent's request carries its own git-worktree cwd (which per CL-4929 can be a sibling directory of the session root, not a subdirectory). Three sites compared these by strict ===, so a worktree cwd could never match a project grant — meaning project-scoped approvals never actually covered the sub-agents that generate most of the approval volume.
  • Adds cwdMatchesGrant (src/permission/authz-grants.ts), a single shared helper resolving membership through the gate's own rootsProvider (the existing git worktree list-backed registry already used for path containment) instead of comparing raw path strings. A grant only gets "one of my worktrees" leniency when its stamped cwd equals the current gate's own session root; membership within that project is exact equality against realpath-resolved roots, never a path-prefix (blocks a /repo/wt-1-evil vs /repo/wt-1 spoof).
  • Threaded into the three previously-inline comparisons: evaluateApprovals, isRequestCoveredByGrant, hasExactFullCommandGrant.

Security boundary note

This is a security-relevant change to grant matching. The fix does not loosen the cwd filter — it still confines a project grant to its own project. It only teaches the matcher that a sub-agent's own worktree is part of that project. Reviewed by Greybeard, Critique, gaasbot, bruckheimer, and neckbeard specifically on whether this could let a grant cross project boundaries; no leak found by any reviewer. grantCwd !== workspace.resolvedCwd rejects any grant not belonging to the current gate's own project before roots are even consulted.

Related to CL-5671 (a separate, already-ticketed bug where classify.ts's shell auto-allow containment check is not worktree-aware, unlike path-restriction.ts). This fix reuses the existing worktree-roots authority rather than adding a new definition of "inside the workspace"; full unification of classify.ts with path-restriction.ts is scoped to CL-5671 as a separate PR.

Test plan

  • Unit tests in evaluateApprovals covering: worktree match, unrelated-project non-match (security test), session/provider-model scopes unaffected by workspace
  • Integration tests through createPermissionGate using a real git worktree add sibling directory: project grant minted at session root matches a sub-agent request from the worktree; does not match a request from a genuinely unrelated directory; session-scoped grant matches regardless of cwd
  • bun run test green (4084 pass, 1 pre-existing unrelated failure in lsp-availability.test.ts — checks real devDependencies presence, not touched by this change)
  • bun run typecheck at pre-existing baseline (119 errors, none in touched files)

https://linear.app/abklabs/issue/CL-5662

mintGrant stamps a project grant's cwd with the session root, but a
sub-agent's request carries the cwd of its own git worktree (which can
live outside the session root as a sibling directory, per CL-4929). The
three sites that compared grant.cwd to request.cwd by strict equality
(evaluateApprovals, isRequestCoveredByGrant, hasExactFullCommandGrant)
meant a worktree path could never equal the session root, so no
project-scoped grant ever matched a sub-agent request.

Added a shared cwdMatchesGrant helper that resolves membership through
the gate's existing rootsProvider (the authoritative registry for "which
worktrees belong to this session," already used for path containment) instead
of comparing raw path strings. A grant only gets this leniency when its
stamped cwd matches the current gate's own session root, so a grant still
never crosses project boundaries; membership within a matching project is
exact equality against the resolved worktree roots, never a path-prefix.
@linear-code

linear-code Bot commented Aug 8, 2026

Copy link
Copy Markdown

CL-5662

CL-5671

@TheGreatAxios
TheGreatAxios merged commit 051e527 into main Aug 8, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant