Safeguard
AI Security

Least-Privilege Tool Scoping for AI Coding Agents

One overprivileged GitHub token let researchers hijack an AI agent into leaking private repo data via a public issue. Scoping tool access closes that gap.

Safeguard Research Team
Research
7 min read

In May 2025, researchers at Invariant Labs disclosed a working attack against Anthropic's own MCP integration pattern: an AI agent connected to GitHub's official MCP server could be hijacked by a single malicious issue filed on a public repository, and because the agent held one GitHub token spanning both public and private repos, the injected instructions walked it straight into a private repository, pulled salary data and other sensitive details, and exfiltrated them into a publicly visible pull request. Nothing about the MCP server's code was buggy — the flaw was architectural. One credential crossed a trust boundary it should never have been allowed to cross. Docker's security team wrote up the same incident as a cautionary tale for anyone wiring agents into their infrastructure, and GitHub tracked the underlying issue as #844 on its github-mcp-server repository. The fix Invariant demonstrated wasn't a patch to the MCP server at all — it was scoping the token itself, binding each agent session to a single repository so an injected instruction had nowhere to pivot. This post is a practical guide to that same discipline: scoping filesystem, network, and shell access for coding agents so a successful prompt injection stays a contained incident instead of a full compromise.

Why does prompt injection turn into a permissions problem?

Prompt injection turns into a permissions problem because the attacker doesn't need to break the model — they only need it to call a tool it's already allowed to call. An AI coding agent reading a GitHub issue, a pull request comment, a README, or a scraped web page has no reliable way to distinguish "instructions from my operator" from "text that happens to look like instructions," and every serious agent vendor treats this as unsolved rather than patched. The GitHub MCP case is the clean illustration: the agent didn't need a jailbreak to leak private data, it just needed one tool call — read a private repo, write a public PR — that its existing GitHub token already authorized. Elastic Security Labs has documented the same pattern more broadly across MCP-connected agents, naming over-broad tool grants and "tool poisoning" (malicious or lookalike tool descriptions) as a distinct attack class rather than an edge case. If the credential can do it, an injected instruction can eventually make the agent do it — which means the permission boundary, not the prompt, is the actual control.

What makes MCP's permission model risky by default?

MCP's default risk is structural: most MCP servers grant access per-server, not per-tool or per-resource, so connecting one server to an agent implicitly hands it every tool and every scope that server exposes. A filesystem MCP server typically doesn't distinguish "read this one config file" from "read and write anywhere on disk" once it's connected; a GitHub MCP server typically doesn't distinguish "this one repo" from "every repo the underlying token can see." That's exactly the gap the Invariant Labs researchers exploited — the GitHub MCP server behaved correctly by its own logic, but the logic was "if the token permits it, do it," and the token permitted far more than the task required. This means least privilege can't be enforced by the server alone; it has to be enforced by whatever sits between the model and the tool call — the client, the host application, or a policy layer — because the server was never designed to know which of its capabilities a given task actually needs.

How does Claude Code's permission model scope tool access in practice?

Claude Code's settings.json permission model is a concrete example of least-privilege scoping built into an agent host rather than left to the model's judgment. Rules live under permissions.allow, permissions.deny, and permissions.ask, evaluated in that priority order — deny wins over ask, which wins over allow — with the first matching rule deciding the outcome, so a narrow deny rule can override a broad allow. Rules aren't just per-tool; they support argument-level patterns, so you can permit Bash(npm install) without opening every shell command, or permit Write(src/**) without granting write access outside the project tree. Filesystem access is restricted by default to the current working directory, with any additional paths requiring an explicit entry in additionalDirectories. Permission modes — default, acceptEdits, plan, and bypassPermissions — then set how aggressively shell, write, and network-capable tools get gated for a given session, letting a team run a read-only planning session with one mode and a supervised edit session with another.

What should a team actually restrict first?

Three categories carry the most real-world risk, in rough order of how often they show up in documented incidents: shell execution, outbound network access, and write access outside the task's actual scope. Shell access is the highest-leverage target for an attacker because a single injected curl | sh pattern or package-manager postinstall script can do anything the agent's underlying OS user can do — scope it to an explicit allowlist of commands rather than blanket shell access. Outbound network access deserves the same treatment: an agent that can reach arbitrary URLs can be instructed by injected content to exfiltrate whatever it just read, which is precisely the mechanism in the GitHub MCP case, just substituting a webhook for a pull request. Filesystem writes should be scoped to the working directory or an explicit allowlist, never the whole home directory or system paths, since Claude Code's own default (working directory plus opt-in additionalDirectories) reflects exactly this reasoning. Credentials passed to any of these tools should be session-scoped and narrowly bound — one repository, one bucket, one API surface — so that even a successful injection has a small, auditable blast radius rather than access to everything the underlying token could theoretically reach.

How should teams verify their scoping actually holds?

Verification means testing the boundary the same way an attacker would probe it, not just reading the config and assuming it works. That means running a deliberately adversarial prompt-injection payload through a sandboxed copy of the agent's actual toolset — an issue comment, a scraped page, or a file the agent will plausibly read during normal operation — and confirming the deny rules and scope restrictions actually block the pivot, not just the obvious case. GitHub's own advisory on issue #844 and Invariant's writeup are useful test scripts in themselves: replaying that exact scenario (public-repo injection attempting to reach a private repo) against your own MCP-connected agents is a concrete, repeatable check rather than a theoretical one. Treat the permission configuration as part of your normal change-review process, since a well-intentioned additionalDirectories entry or a loosened Bash pattern added to unblock one task can silently widen the blast radius for every task after it.

How Safeguard fits into this picture

Safeguard's own MCP server, which connects Claude and other assistants to your software supply chain data, is itself scoped per tenant through OAuth or an API key generated per account — an agent connected to it can only reach the vulnerabilities, SBOMs, and findings that credential is authorized for, not another tenant's data. That's a narrower example of the same principle this post describes: the credential you hand an agent should map exactly to the task, not to everything the underlying account could theoretically touch. Applying that same discipline to your own coding agents' filesystem, network, and shell permissions — scoped credentials, explicit allowlists, and adversarial verification against known injection patterns like the GitHub MCP case — is what keeps a single hijacked tool call from becoming a tenant-wide incident.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.