You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Local agents run on the maintainer's own credential. Constrain them with a
dedicated GitHub identity holding least-privilege rights instead, so "an agent
may not do X" becomes a property the token cannot express rather than a command
pattern someone has to think of.
The problem
.claude/settings.json tries to express agent authority by pattern-matching
command strings. That language cannot express the property we actually want. #620 is three consecutive rounds of proving it: Stage 4 found two git push
holes, the maintainer found two more by hand (git push origin refs/tags/v1.2.3, git push origin HEAD — which pushes main when you are on main), and the
review's own conclusion was that prefix globs cannot express "no valid spelling
reaches a protected branch". The same shape broke main: #657 replaced the
blanket Bash(gh api) / Bash(gh api *) ask rules with ten enumerated forms,
and quality-check.sh's gate — which requires the blanket spelling precisely
because the dangerous markers sit at arbitrary argument positions — now fails on
a clean origin/main export.
The enumeration keeps leaking because it is carrying weight it cannot carry.
Measured starting state
Both agent accounts exist, but their tokens are as powerful as the maintainer's.
All three are classic PATs carrying repo (full repository control):
gh-agent.sh only changes attribution, not authority. It swaps the token
for a single gh call and exits. Every git push, gh pr create and gh api an agent makes still runs on the maintainer's credential.
bess-agent can delete published GHCR packages (delete:packages) and
rewrite .github/workflows/* (workflow). A token that can edit a workflow
can grant itself anything, since the workflow runs with repo secrets.
The merge gate is prose, and now measured to be
Protect Main Branch (ruleset 17568935, enforcement: active) has an empty bypass_actors list — so direct pushes to main are genuinely blocked for
everyone including the owner. Good. But its pull_request rule reads:
Zero approvals required. Any token with pull_requests: write can open a PR and
immediately merge it. "Never merge, ever" is currently enforced by nothing but
skill prose.
The blanket-vs-enumeration argument over Bash(gh api …): a token without
Administration or Actions gets 403 on the destructive calls regardless of
argument order, so the ask list stops being the guard and becomes belt-and-braces.
What this does NOT cover
State these explicitly so the token is not mistaken for a general safety net:
Correctness. Nothing here constrains what an agent writes, only what it
can reach.
Cost to accept
An agent can no longer push changes to .github/workflows/*. #645 edits issue-analyze.yml, so under this scheme that change is applied by the
maintainer by hand. This is a deliberate trade, not an oversight — but it does
change the workflow and should be agreed rather than discovered.
Open questions to verify before committing
Do fine-grained PATs cover Projects v2 for a user-owned project? BESS_PO_TOKEN currently relies on classic project scope, and the backlog
board (Project . #1) is user-owned. Verify against the live board, do not assume.
How should the merge gate actually be enforced? Raising required_approving_review_count to 1 with require_last_push_approval: true
would stop an agent merging its own work — but bess-reviewer is a separate
identity and could approve, so confirm what that composition actually yields
before relying on it.
Does git push pick the token up? The credential must govern git, not
just gh — setting GH_TOKEN for agent sessions rather than only inside gh-agent.sh.
Definition of Done
Fine-grained PATs minted for both roles with the tables above
BESS_AGENT_TOKEN / BESS_PO_TOKEN repointed; classic PATs revoked
GH_TOKEN wired for agent sessions so it governs git push too
One ordinary issue driven end to end through implement-issue on the new
token, with whatever 403s it hits recorded — the real permission surface
measured, not guessed
Merge gate decision made and applied (open question 2)
Summary
Local agents run on the maintainer's own credential. Constrain them with a
dedicated GitHub identity holding least-privilege rights instead, so "an agent
may not do X" becomes a property the token cannot express rather than a command
pattern someone has to think of.
The problem
.claude/settings.jsontries to express agent authority by pattern-matchingcommand strings. That language cannot express the property we actually want.
#620 is three consecutive rounds of proving it: Stage 4 found two
git pushholes, the maintainer found two more by hand (
git push origin refs/tags/v1.2.3,git push origin HEAD— which pushesmainwhen you are onmain), and thereview's own conclusion was that prefix globs cannot express "no valid spelling
reaches a protected branch". The same shape broke
main: #657 replaced theblanket
Bash(gh api)/Bash(gh api *)ask rules with ten enumerated forms,and
quality-check.sh's gate — which requires the blanket spelling preciselybecause the dangerous markers sit at arbitrary argument positions — now fails on
a clean
origin/mainexport.The enumeration keeps leaking because it is carrying weight it cannot carry.
Measured starting state
Both agent accounts exist, but their tokens are as powerful as the maintainer's.
All three are classic PATs carrying
repo(full repository control):johanzandergh auth(keyring)gist, project, read:org, repo, workflowbess-product-ownerBESS_PO_TOKENproject, repobess-agentBESS_AGENT_TOKENrepo, workflow, write:packages, delete:packages, notificationsTwo things fall out of that table:
gh-agent.shonly changes attribution, not authority. It swaps the tokenfor a single
ghcall and exits. Everygit push,gh pr createandgh apian agent makes still runs on the maintainer's credential.bess-agentcan delete published GHCR packages (delete:packages) andrewrite
.github/workflows/*(workflow). A token that can edit a workflowcan grant itself anything, since the workflow runs with repo secrets.
The merge gate is prose, and now measured to be
Protect Main Branch(ruleset17568935,enforcement: active) has an emptybypass_actorslist — so direct pushes tomainare genuinely blocked foreveryone including the owner. Good. But its
pull_requestrule reads:{"required_approving_review_count": 0, "require_last_push_approval": false, ...}Zero approvals required. Any token with
pull_requests: writecan open a PR andimmediately merge it. "Never merge, ever" is currently enforced by nothing but
skill prose.
Proposed permission sets
Fine-grained PATs, one per role:
bess-developer— pushes branches, opens PRs, comments:delete:packagesbess-product-owner— never pushes code:What this makes redundant
already proposed deleting, on the same reasoning generalised beyond
git push.Bash(gh api …): a token withoutAdministration or Actions gets 403 on the destructive calls regardless of
argument order, so the ask list stops being the guard and becomes belt-and-braces.
What this does NOT cover
State these explicitly so the token is not mistaken for a general safety net:
rm -rf, the worktree destruction in fix: stop the fleet prune from destroying the worktrees it cannot remove #653. AGitHub token has no opinion on those; that stays the OS sandbox's job. Two
separate surfaces.
review round. That is what feat: enforce review-loop legality and derive fleet state from GitHub #638's and feat: give issues and PRs an explicit state machine, so the backlog loop can progress work #658's review-loop gates are for, and
this issue does not resolve their disagreement.
can reach.
Cost to accept
An agent can no longer push changes to
.github/workflows/*. #645 editsissue-analyze.yml, so under this scheme that change is applied by themaintainer by hand. This is a deliberate trade, not an oversight — but it does
change the workflow and should be agreed rather than discovered.
Open questions to verify before committing
BESS_PO_TOKENcurrently relies on classicprojectscope, and the backlogboard (Project . #1) is user-owned. Verify against the live board, do not assume.
required_approving_review_countto 1 withrequire_last_push_approval: truewould stop an agent merging its own work — but
bess-revieweris a separateidentity and could approve, so confirm what that composition actually yields
before relying on it.
git pushpick the token up? The credential must govern git, notjust
gh— settingGH_TOKENfor agent sessions rather than only insidegh-agent.sh.Definition of Done
BESS_AGENT_TOKEN/BESS_PO_TOKENrepointed; classic PATs revokedGH_TOKENwired for agent sessions so it governsgit pushtooimplement-issueon the newtoken, with whatever 403s it hits recorded — the real permission surface
measured, not guessed
settings.jsonenumeration reduced to whatever is still load-bearingafterwards, and fix: guard git push by shape so feature-branch pushes run unattended #620 re-scoped against the result