Skip to guide
ver·tias/ PassControlAll guides

Integration guide

GitHub Access for AI Agents Without Sharing Your Token

Most agents that touch GitHub are given a personal access token in an environment variable, which hands every copy of the agent the whole token. This guide shows how to keep the token out of the agent while still letting it read what it needs.

Written for: Agent builders, plugin and MCP server authors, and platform engineers

The usual setup puts your token inside the agent

An agent that triages issues, summarises pull requests or reads a repository needs to call the GitHub API, and the quickest way to allow that is to create a personal access token and put it in the agent's environment. It works on the first try, which is why it is everywhere.

It also means the token is readable by everything the agent runs: its own code, the libraries it imports, the scripts it writes for itself, and any text that manages to steer it. A token is a bearer credential, so whoever reads it can use it from anywhere, for as long as it is valid, with every permission it carries. The agent's instructions limit what it intends to do; they do not limit what the token allows.

Ask what the token can do, not what the agent is supposed to do. The first is the real limit.

A fine-grained token is the ceiling, not the whole plan

GitHub's fine-grained personal access tokens can be limited to chosen repositories and to read-only permissions, and that is the right first step: create one per purpose, never reuse your own everyday token, and give it an expiry. If it leaks, the damage is bounded by what you granted.

But a token is one grant shared by everything that holds it. If three agents use the same token, each can do everything the token allows, and GitHub's audit log shows one identity for all of them. A fine-grained token sets the most any agent could ever do. It cannot say that the triage agent may read issues in one repository while the release agent may read tags in another.

Per-agent rules are the floor

The missing layer is a rule per agent, checked on every call, outside the agent. The shape that works for a REST API is a method and a path pattern: an agent is allowed GET on /repos/acme/*/issues and nothing else. Anything not named is refused before it reaches GitHub, so an agent with no rules cannot make a single call.

Two properties make these rules trustworthy. They are read live, so removing a rule stops the next call instead of the next deployment. And if the rules cannot be read, because a database or cache is unavailable, the call is refused rather than allowed, since allowing it would silently hand the agent the whole token.

  • Deny by default: an agent starts with no GitHub access at all.
  • Name the method and the path; keep wildcards to one path segment where you can.
  • Cap calls per agent per hour, so a looping agent cannot exhaust your API quota.
  • Give each agent its own credential, so a refusal or a call can be attributed to it.

The wire details a generic proxy gets wrong

Putting a gateway between an agent and GitHub is simple in outline, and GitHub has details that decide whether the rules actually hold. GraphQL is a single endpoint that can do anything the token can, so a path rule cannot scope it; the safe answer is to refuse it rather than pretend. Pagination links in responses point back at api.github.com, so a gateway must rewrite them to itself, or page two of a list quietly leaves the controlled path.

Redirects need care too. Some GitHub responses redirect to another host, and a gateway that follows a redirect while carrying the token sends the token somewhere it was never meant to go. Handing the redirect back to the agent, unauthenticated, keeps the token on one host. Finally, GitHub's own clients send the credential as Authorization: token rather than Bearer, so a gateway that expects only Bearer forces every client to be patched.

Plugins and tool servers: make the base URL configurable

Agent plugins and MCP servers usually receive credentials through environment variables or a configuration file, and a GitHub tool server typically builds api.github.com addresses itself. In that design, putting a gateway credential in the variable does not help: the server still calls GitHub directly, and GitHub rejects a credential it did not issue. The gateway is only in the path if the server lets the operator choose where GitHub calls go.

If you write a tool server or plugin that calls GitHub, accept a base URL setting and pass whatever credential the operator supplies. That one option lets the operator put the real token behind a gateway, give each agent its own revocable credential, and see every call, without any change to your code. Code an agent writes and runs for itself is the easy case: a client such as Octokit takes a base URL and a credential, and nothing else changes.

Start read-only, and make write rules exact

Reads and writes fail differently. A read rule that is too broad exposes something the agent should not have seen. A write rule that is too broad does something: it comments, closes, labels or deletes. Begin with read-only access and add writes one action at a time, each naming a single method and path, with no trailing wildcards. The token's own permissions remain the outer limit either way.

PassControl works this way for GitHub today. The token is stored once in its vault, and each agent gets its own rules for GET and HEAD calls, checked live and refused when unreadable, with an hourly call cap. GraphQL is refused, pagination links are rewritten, redirects are handed back, and the agent authenticates with its own PassControl credential, including in GitHub's token scheme. A real GitHub token sent by the agent is refused and never forwarded, and every call is recorded with the rule that admitted it and a signed receipt.

Frequently asked questions

Short answers to the questions that matter.

Does the agent ever see the GitHub token?

Not when the token lives behind a gateway. The agent holds only its own credential, which is useless against GitHub directly. The gateway checks the agent's identity and rules, then adds the stored token to the outgoing request, so the token exists only inside the gateway.

Can an agent use GitHub's GraphQL API this way?

Not safely with path rules. GraphQL sends every operation to one endpoint, and the operation itself is in the request body, so a rule on method and path cannot tell a read from a write. Refusing it is the honest control; the REST API covers the same reads with paths that can be scoped.

What if an agent sends its own GitHub token anyway?

A gateway should refuse it rather than pass it through. Forwarding a caller-supplied token would let an agent bypass every per-agent rule with a token of its own, so the only credential that ever reaches GitHub should be the one the operator stored.

Is a fine-grained token still worth creating?

Yes. It is the ceiling on what any agent could ever do, and it limits the damage if the gateway's store is ever exposed. Per-agent rules narrow access further; they do not replace a least-privilege token.

Primary sources

Further reading from standards and security bodies.