The short answer
Turn on read-only mode, list only the toolsets the agent needs, and give it a fine-grained personal access token limited to the repositories it works on. Locally that’s --read-only --toolsets repos,issues, or GITHUB_READ_ONLY=1 and GITHUB_TOOLSETS in Docker. Remotely it’s a /readonly URL or the X-MCP-Readonly header. Add lockdown mode if the agent reads public issues, but don’t count it as a security control.
Deedglass
A tool call meets your rules before it runs
Asks to run
DELETE FROM visits;
Deletes every row in visits
Deedglass checks your rules
- Reading dataAllowed
- Deleting a whole table (DROP, TRUNCATE)Never
- Changing or deleting rows without saying which (WHERE)This one
- Other changesWait for a person
What you get by default
With no toolsets named, the server loads its default toolset: context, repos, issues, pull_requests and users.2 That covers creating branches, pushing files, opening and merging pull requests, and commenting on issues, which is more than an agent that only needs to read code and triage issues should have.
The reason this matters is well documented. In May 2025, Invariant Labs showed a malicious issue in a public repository steering an agent with the GitHub server connected into copying data from the user’s private repositories into a public pull request. They called it an architectural problem at the agent level, not a flaw in the server code.4 Our write-up of that exploit goes through it step by step. This post is the setup side: which switches exist and what each one actually limits.
The four controls
| Control | Local flag | Docker env var | Remote | What it limits |
|---|---|---|---|---|
| Read-only | --read-only |
GITHUB_READ_ONLY=1 |
/readonly URL suffix or X-MCP-Readonly: true |
Only read tools are offered12 |
| Toolsets | --toolsets repos,issues |
GITHUB_TOOLSETS |
/mcp/x/<toolset> URL or X-MCP-Toolsets |
Which groups of tools load12 |
| Lockdown | --lockdown-mode |
GITHUB_LOCKDOWN_MODE=1 |
X-MCP-Lockdown |
Public content from people without push access12 |
| Token scope | the token you pass | the token you pass | your OAuth grant | What the credential can reach at all3 |
Two precedence rules are worth knowing. Read-only wins over an explicit tool list: write tools are skipped under --read-only even if --tools names them. And GITHUB_TOOLSETS takes precedence over the --toolsets flag when both are set.1 For the remote server, a request header can turn lockdown on but can’t turn off lockdown the operator already set.2
The special toolset all enables every toolset.1 If you see it in a config, ask why.
Recipes
A local agent that reads code and issues, in Docker:
Code Data in JSON, the format software uses to pass records and settings.
{
"mcpServers": {
"github": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "GITHUB_PERSONAL_ACCESS_TOKEN",
"-e", "GITHUB_READ_ONLY=1",
"-e", "GITHUB_TOOLSETS=repos,issues",
"ghcr.io/github/github-mcp-server"
]
}
}
}
The -e GITHUB_PERSONAL_ACCESS_TOKEN line with no value passes the variable through from your environment, so the token isn’t written into the file.
The remote server, read-only, issues only:
Code Data in JSON, the format software uses to pass records and settings.
{
"mcpServers": {
"github-issues": { "url": "https://api.githubcopilot.com/mcp/x/issues/readonly" }
}
}
The remote server with headers instead of a URL:
Code Data in JSON, the format software uses to pass records and settings.
{
"mcpServers": {
"github": {
"url": "https://api.githubcopilot.com/mcp/",
"headers": {
"X-MCP-Toolsets": "repos,issues",
"X-MCP-Readonly": "true",
"X-MCP-Lockdown": "true"
}
}
}
}
The headers key is accepted by clients that support HTTP headers for MCP servers; check your client’s format in our table of MCP config files.
What lockdown does, and doesn’t
Lockdown mode checks whether the author of each item in a public repository has push access, and withholds or errors on content from people who don’t. Content from github-actions[bot] and copilot is treated as safe. The README is explicit that it is not an authorization boundary: the credential’s access doesn’t change, and withheld content may still be reachable through other tools or the API with the same token.1
So lockdown narrows what untrusted text reaches the model. It doesn’t narrow what the agent can do once something does reach it. For an agent that reads public issues and holds a token with write access to private repositories, that’s still Simon Willison’s “lethal trifecta”: private data, untrusted content and a way to communicate externally.5 The fix is to remove a leg, usually the write access.
The token is the real boundary
Every switch above is a setting on the server. The token decides what’s possible. GitHub’s fine-grained personal access tokens can be limited to specific repositories and given specific permissions, and GitHub recommends the minimum access that meets your needs. They always include read access to all public repositories.3
A checklist for the token an agent uses:
- Fine-grained, not classic.
- Repository access set to “only select repositories”, naming the one or two the agent works on.
- Contents: read-only, unless the agent must push.
- Issues and pull requests: read-only, unless it must comment.
- No administration, secrets, workflows or organization permissions.
- An expiry date you’ll notice.
Invariant’s own advice after the exploit was to limit an agent to one repository per session.4 A token scoped to one repository enforces that even when the agent is persuaded to try otherwise.
Checking a config
If you run a config scanner over your MCP files, check what it does with GitHub. In our runs of deedglass 0.13.0’s scan, the GitHub server contributed no write paths to the grade, with or without read-only mode, because the scan’s catalog doesn’t classify that server’s tools.6 A good grade there doesn’t mean the GitHub entry was reviewed. Read it against the table above yourself.
Questions people ask
Does the GitHub MCP server have a read-only mode?
Yes. --read-only locally, GITHUB_READ_ONLY=1 in Docker, a /readonly URL suffix or the X-MCP-Readonly header remotely. It offers only read tools and overrides any explicit tool list.12
Is lockdown mode enough to stop prompt injection?
No. GitHub’s README calls it a best-effort content filter, not an authorization boundary; the token can still read and write what it could before.1
Which toolsets load if I don’t choose any?
The default toolset: context, repos, issues, pull_requests and users.2
For the broader picture, see prompt injection, the lethal trifecta explained, the MCP security checklist and the ten-minute MCP config audit.
To see everything else your agent setup can change, run npx deedglass scan in the repository (what the grade means), or watch the checks in the playground.
Written with AI assistance and checked against our claims register.