GitHub Token Scoping for Agents That Open Pull Requests
Limit agent GitHub tokens to the three permissions a pull request actually requires.

A GitHub token given to an agent that opens pull requests defines the total scope of what that agent can do, both intended and unintended. Scoping that token is an ongoing containment decision, not a checkbox on a setup form, one that has to be re-evaluated every time the agent's job changes, every time a workflow gets copied to a new repository, and every time a token gets renewed. It's an ongoing containment decision, one that has to be re-evaluated every time the agent's job changes, every time a workflow gets copied to a new repository, and every time a token gets renewed. Get the scope wrong and the agent will use whatever's available to it, because agents don't exercise the kind of contextual restraint a human developer applies without thinking about it.
That distinction matters more now than it did two years ago, because agents aren't experimental anymore. Over half of organizations report having agents in production, and adoption inside individual engineering orgs has moved fast: Claude Code usage at Uber's roughly 5,000-engineer organization reportedly jumped from 32% to 84% between December 2025 and March 2026. A human developer opens a handful of pull requests a day and thinks twice before pushing to main. An agent can open dozens of PRs in an hour, following instructions literally, with no internal sense of "that seems like too much." Whatever permission it holds, it will eventually use. At that scale, a misconfigured token is an exposure multiplied by every single invocation across the org. It's an exposure multiplied by every single invocation across the org.
How the three token types differ before you write a single permissions block
Three credential types appear in any agent workflow that touches pull requests, and they are not interchangeable.
Classic personal access tokens are the oldest and broadest. A classic PAT with the repo scope grants persistent access to every private repository the token's owner can reach, and per GitHub's own documentation, that access lasts indefinitely unless someone manually revokes it. Handing an agent a classic PAT means handing over the entire GitHub account to an external interpreter that has no idea it was only supposed to open pull requests on one repository. The token just knows what it's allowed to touch, and with a classic PAT, that's nearly everything. It just knows what it's allowed to touch, and with a classic PAT, that's nearly everything.
Fine-grained PATs came along specifically to fix this. They expose over 50 granular permissions, each one selectable at install time and scoped to specific repositories rather than inherited wholesale from the user's account. They also expire automatically, with a maximum lifetime of 366 days. Fine-grained PATs have since reached general availability, and they represent one of the only two real paths to least-privilege for an agent.
The other path is GITHUB_TOKEN, minted automatically for every GitHub Actions workflow run with zero configuration required. That convenience cuts both ways. Absent an explicit permissions: block in the workflow YAML, the default permissions are broader than most teams assume. A CSA research note pointed out that a stolen GITHUB_TOKEN from a widely used open-source repository can establish a foothold for a supply chain attack for as long as that token stays valid.
The conceptual line to draw here is simple: fine-grained PATs and GITHUB_TOKEN with an explicit permissions: block are the only two credential types compatible with real containment thinking. Classic PATs are structurally incapable of it, no matter how carefully someone tries to use them responsibly.
The credential landscape is still shifting. GitHub Agentic Workflows entered public preview on June 11, 2026, using GITHUB_TOKEN natively with no PAT required at all. That's a change from the earlier technical preview in February 2026, which still required a fine-grained PAT to function. Scoping discipline is a moving target that teams have to keep re-checking against whatever GitHub ships next. It's a moving target that teams have to keep re-checking against whatever GitHub ships next.
The specific permissions a PR-opening agent needs, and the reasoning behind each one
Start with what the agent is actually supposed to do: create a branch, push commits to it, open a pull request, comment on that PR. That's the job, and everything else is optional at best and dangerous at worst. Everything else is optional at best and dangerous at worst.
Three permissions cover the actual job. contents: write lets the agent create branches and push commits; without it, there's nowhere for the agent's code to go. pull-requests: write lets it open the PR and comment on it, which is the entire point of the exercise. metadata: read is an unavoidable baseline needed for normal repository discovery. That's the full list for a PR-opening agent. Nothing more belongs on it.
What shouldn't be on it is a longer list, and each omission has a reason behind it. actions: write or workflows: write give an agent the ability to trigger or modify CI runs, something a PR-opener has no legitimate reason to do unless that's explicitly part of its job description; withholding it means an injected instruction buried in a PR comment can't launch arbitrary CI jobs on the agent's behalf. issues: write expands blast radius with no corresponding benefit unless issue triage is actually part of the task. checks: write only matters if the agent is publishing check results, which a pure PR-opener isn't. Administration permissions have no justification here at all, since they grant access to branch protection rules and repository settings that have nothing to do with opening a pull request. Secrets permissions are never appropriate for an automated agent in a normal PR workflow, full stop.
For GITHUB_TOKEN specifically, the pattern in workflow YAML should mirror this same minimalism: contents: read and packages: read for setup and read-only steps, with contents: write and pull-requests: write added only to the specific steps that actually need them, not applied blanket across the whole workflow.
A security-reviewer agent, by contrast, needs something different: read access to source code, the ability to interact with pull requests, and permission to report checks. Such an agent has no need to push code or launch workflows. The permission set follows the job, every time, without exception.
The repo scope on a classic token technically covers the same PR-opening workflow, which is precisely the problem. It also grants access to every private repository the user can reach, along with a broad set of account-level capabilities. The blast radius is the user's entire GitHub presence, not the one repository the agent was built to work in. It's the user's entire GitHub presence.
Fine-grained PATs have documented capability gaps in certain account-level resource types. Workarounds that fall back to classic PATs reintroduce exactly the broad access fine-grained tokens were built to eliminate. That's a documented tension with no clean fix currently available.
What happens when the scoping is wrong: three concrete failure modes from disclosed incidents
None of this is theoretical. Disclosed incidents show what happens when scoping gets treated as a formality rather than a control.
The first involved agents reading untrusted pull request content as though it were trusted instruction, no jailbreak required. Confirmed exfiltration targets in Anthropic's finding included ANTHROPIC_API_KEY and GITHUB_TOKEN; across the three vendors involved (Anthropic, Google, and GitHub), the fuller list also covered GEMINI_API_KEY, GITHUB_COPILOT_API_TOKEN, GITHUB_PERSONAL_ACCESS_TOKEN, and COPILOT_JOB_NONCE. The exfiltration path ran through GitHub's own PR comments and issue updates, so there was no external server and no unusual network egress for anyone to catch. Anthropic received the initial report in October 2025 through HackerOne, paid a $100 bounty, and upgraded the severity rating from 9.3 to 9.4. No CVE was assigned. No public advisory followed. The direct link back to scoping is straightforward: a GITHUB_TOKEN limited to pull-requests: write and contents: write on a single repository sharply limits what an attacker can actually do with it, even after successful exfiltration. The damage ceiling is the permission ceiling.
The second failure mode came through Codex, reported by SecurityWeek in March 2026. Researchers found a since-patched vulnerability where malicious GitHub branch names could inject commands during task setup and retrieve GitHub authentication tokens. The injection vector here wasn't PR body text, it was repository metadata. Any field an agent reads during startup counts as an attack surface, not just the obvious ones.
The third involves the tj-actions/changed-files compromise from March 2025, which demonstrated that third-party GitHub Actions are effectively untrusted code dependencies, ones that many organizations had been scrutinizing far less carefully than they scrutinize application libraries. Runner secrets, including PATs and GITHUB_TOKEN, get injected as plain environment variables during a job. GITHUB_TOKEN expires when the job finishes, but longer-lived PATs can persist well beyond the runner's own lifetime. The scoping lesson repeats here: a narrowly scoped token compromised through a supply chain attack simply can't do as much damage as a classic PAT would in the same position.
Across all three incidents, no model got exploited in any traditional sense. The agents did exactly what agents do, following instructions and using whatever permissions someone had already handed them. The permissions were the attack surface the whole time.
How to express token scoping as versioned configuration rather than a one-time setup act
Treating scoping as a one-time setup step breaks down for a predictable reason: agents get updated, workflows get copied to new repos, new agents get onboarded, and permissions drift quietly in the background with nobody watching for it.
The fix is to treat the permissions: block in workflow YAML as code, because it is code. It belongs in version control. It should go through the same PR review as any other change, and it should get audited on the same cadence as the workflow logic sitting around it. In practice, that means defining agent permissions in a YAML config file checked into the repository, so any scope change requires a reviewed commit, leaving behind an audit trail of exactly who expanded what and when.
GitHub Agentic Workflows, first announced in technical preview in February 2026, models this well. Workflow files live in .github/workflows/ right alongside traditional Actions workflows, and permissions, schedule triggers, and model engine authentication all get declared together in YAML frontmatter. A conventional test.yml and an agentic issue-triage.md can run side by side without stepping on each other. As of the June 2026 changelog, Agentic Workflows can use the built-in GITHUB_TOKEN directly, with no PAT needed at all, closing off that particular attack surface entirely; billing gets routed to the organization through a copilot-requests: write declaration in the frontmatter.
Teams that want to go further can skip long-lived secrets altogether by relying on short-lived tokens scoped to individual workflow runs. Pinning third-party Actions to specific commit SHAs, rather than trusting mutable tags that can change underneath a workflow without warning, is another versioning discipline that deserves the same review process as the permissions block itself.
At the organizational level, GitHub lets admins set expiration policies for fine-grained PATs (366 days by default) and, optionally, for classic PATs, which otherwise have no expiration requirement at all. Pairing that enforcement with YAML-governed scoping means every expiration event forces a deliberate re-review of what the renewed token should actually cover, rather than a reflexive renewal of whatever scope it had before.
Before any of this reaches production, testing it in a dedicated low-risk repository first, covering PR, issue, and CI workflows, means the permission grants that eventually reach production are ones someone actually looked at closely.
The operational controls that sit alongside token scoping to close the gaps scoping alone cannot close
Scoping sets the ceiling. It does nothing to stop an agent from using every square inch of permission underneath that ceiling, which raises the real operational question: what does the team actually see when the agent acts?
Observability closes part of that gap. Every tool call, every diff, every token the agent consumes should be logged and traceable back to a specific person or trigger, not purely for forensics after something's already gone wrong, but as the early signal that lets a team catch unexpected permission use before it turns into a breach.
Session isolation closes another part. Agent sessions running in their own sandboxes, with credentials minted fresh per session and revoked the moment the session ends, mean a token compromised through a prompt-injection-style attack expires on its own. The blast radius gets bounded in time, not just in scope.
Cost containment runs on a parallel track. Agentic workflows can consume five to thirty times more tokens per task than a standard chatbot query, so uncapped agent sessions create a budget exposure that's just as real as a security exposure, even if it gets far less attention in most security reviews.
Branch protection adds a human checkpoint that scoping alone can't provide. Draft pull requests plus a required human approval before merge mean an agent holding pull-requests: write still can't land code into production without someone actually looking at it first. Holding the permission is not the same as holding the capability to affect what ships.
Overlooking webhook scope discipline lets untrusted content reach the agent's context window unnecessarily. Subscribing only to the specific events an agent needs to act on, rather than every repository event by default, shrinks the amount of untrusted content that ever reaches the agent's context window in the first place.
The incident described earlier succeeded in part because agents read PR titles and issue bodies as trusted context, with no architectural line drawn between operator instructions and content submitted by anyone with write access to a comment field. System-prompt hardening and input sanitization at the model layer help reduce that risk, but they're not substitutes for narrow token scope. Both layers have to be running at once, because neither one covers the other's blind spot.
A practical decision sequence for teams configuring a PR-opening agent today
Start by writing down the agent's exact job, in specific terms. "Opens PRs" isn't specific enough to scope anything against. "Creates branches, pushes commits, opens draft PRs, and comments on its own PRs in one named repository" is specific enough to build a permissions block around.
From there, the credential choice follows a fairly clean branch. Running inside GitHub Actions calls for GITHUB_TOKEN with an explicit permissions: block, never the inherited defaults. Running outside Actions, where the agent needs to authenticate to GitHub some other way, calls for a fine-grained PAT scoped to the specific repositories in question, capped at the 366-day maximum expiration, with an org-level rotation policy actually enforced rather than left as a suggestion.
For teams using GitHub Agentic Workflows, the picture depends on which version they're on. The February 2026 technical preview still required a fine-grained PAT, issued as COPILOT_GITHUB_TOKEN. From the June 2026 public preview onward, the recommended approach shifts to the built-in GITHUB_TOKEN paired with an explicit permissions: block, the same discipline that applies everywhere else in this piece, just running on newer infrastructure.
Whichever path a team lands on, the underlying test doesn't change. Write the job description first. Grant only what that description requires. Put the grant in version control where it can be reviewed and audited like every other line of code. Scope the token narrowly enough that the worst version of the failure modes described above is one an org can actually survive.


