github_token_secret, or your GitHub App installation when it stores none. An
agent dispatched to triage one workspace’s issues holds write access to
everything else for as long as it runs.
Repository confinement closes that gap. It is an additional restriction, never a
grant: a confined agent’s GitHub access is limited to the repositories its spawn
already involves, whichever credential serves it. What the agent may do inside that
set is unchanged — still bounded by the serving credential’s own permissions, your
roles and bindings, and any restriction on the identity it
borrows.
Turn it on
Pass one flag. You never list repositories — the set is derived from what the spawn already declares, so it cannot drift from what the agent actually clones.--repo entries, the upstreams those entries open pull requests against, and the
workspace’s plugin repos. Each one is pinned to its stable GitHub repository id
at spawn time, so a repository renamed later is still the same repository to the
platform — and a rename cannot bring a different one into the set.
Confinement is sticky per slug. Once an agent is restricted, later spawns of
that slug stay restricted whether or not the flag is passed again, and nothing
lifts it. To run the same work unconfined, use a different slug.The repository set is recomputed on every top-level spawn, so a resurrect
picks up a repository the workspace gained in the meantime. A child agent is the
exception: it inherits its parent’s set as the parent had it pinned, so a
repository added to the workspace afterwards is not in reach until the parent
itself is spawned again.A flight on a
schedule (cron) trigger follows the same rule at its own moment:
each firing computes its set when it fires, from the workspace as it stands then,
so a repository added to the workspace is in reach at the next tick without
rewriting the flight.What a confined agent can do
- Repository access is limited to the set. An operation naming a repository
outside it is refused, and so is one that reaches across repositories without
naming any (
GET /user/repos, a global code search). Metadata and preflight requests that name no repository still go through, because their response carries no repository data. - The VM holds no GitHub token. There is no
GH_TOKENon the machine for an agent — or a prompt injection — to use directly. Each operation is authorized and credentialed individually by the platform, through the GitHub API gateway. - Children inherit the set exactly. A child agent works in its parent’s workspace and repos, so it gets the parent’s pinned set verbatim. A child cannot narrow it, widen it, or supply repositories of its own.
What changes for gh
Most of what an agent does over gh is unaffected: gh repo view, gh issue list,
gh label list, gh pr checks, and the mutations that act on one item — close,
reopen, ready, review, label, comment — name one repository and touch nothing else.
Some commands ask GitHub for more than the repository they name. gh pr view reads
the head repository of a fork and the account behind it; gh issue view reads an
issue’s parent and sub-issues, which GitHub allows to live in any repository. On
your own GitHub credential those reads are refused rather than narrowed, so the
platform provides an explicit repository-local form to ask for the part it can
serve:
- Without
--json, the confined form requests a field set that stays inside the repository and prints it as text — number, title, state, author, branches, diff size, review decision, mergeability, labels, assignees, timestamps, URL, body. - With
--json, your field set passes through exactly as given (with--jqor--templateif you use them), because that output feeds a program. A field that reaches outside the repository is refused by name rather than dropped from your set.
--confined is available on gh pr view and gh issue view. Commands whose
purpose is to look across an account have no repository-local form: gh pr status runs two global searches, which no single-repository request answers.
gh pr create needs a different route
Creating a pull request is the one write worth calling out. The create mutation
itself is repository-local, but gh pr create does not start there: it first
resolves the branch to any existing pull request, reads the repository network to
find the base repository, and — with --reviewer @org/team — looks up the team.
Those reads go to a fork’s parent, the fork network, and another account, so the
command can be refused before it ever reaches the mutation.
Two routes work under confinement:
-
The repository-scoped REST endpoint, which names one repository in its path
and is therefore bounded by construction:
- An agent served by a GitHub App installation token — a flight running as a service profile that stores no GitHub token of its own — where the pinned token also serves the repository-local lookups the command makes first.
--reviewer @org/team: a token pinned to a repository does not
make organization and team lookups work, and they reach outside the set on any
credential.
Reading a refusal
A refused operation says which rule refused it and what to do instead.Which credential serves a request
Confinement leaves credential selection alone — the usual precedence still decides which credential serves. What changes is how that credential is bounded:- Your own GitHub credential — a developer agent’s token, or a service profile’s stored token — serves a request whose reach is provably inside one repository in the set. A request reaching outside it is refused, with the message naming the field that put it outside and, where one exists, the confined form to run instead.
- A GitHub App installation token, which serves an agent that stores no GitHub credential of its own, is minted pinned to the repository the request names. The restriction rides on the token itself, so GitHub refuses anything outside the pin.
Limits worth knowing before you turn it on
- Every repository in the set must be visible to your GitHub App installation.
The set is pinned to stable ids at spawn, so a member the App cannot see rejects
the spawn rather than producing an agent that cannot clone. The refusal names the
repository; it came from the workspace’s repos, a
--repoentry, one of their pull-request upstreams, or a workspace plugin — a plugin installed from outside the installation is the common case. - Not available with
--propose. A stagedSpawnActionchange-request carries the flags listed onmurmur spawn, and this is not one of them.
Related
- Agent VM controls — the three legs of agent VM security; confinement narrows the first.
murmur spawn— the flag and its errors.- Flight — the
restrict_repository_accessfrontmatter field. - Service profiles — the identity an automated agent runs as.