Defense & Governance · 7 min
Least Privilege for Agents: Scoping Tools and Credentials
An over-privileged agent acting on attacker-controlled input is a confused deputy; scoping tools, credentials, and identity to the end user shuts it down.
An agent is software that holds credentials and calls tools on your behalf, and it decides which tool to call by reading text. That text is often attacker-influenced: a support ticket, a fetched web page, a database row, the contents of a repo it was told to summarize. Put those two facts side by side and the core risk falls out. The agent has authority. The instruction to use that authority arrives as data. Whoever controls the data controls the authority.
This is not a new bug. It is the confused deputy, named by Norm Hardy in a 1988 paper about a compiler on a shared timesharing system. The compiler ran with permission to write to a protected billing file in the system directory. It also let users name an output file for its optimization statistics. Name the billing file as your "output," and the compiler, the deputy, would overwrite it using its authority rather than yours. Nobody escalated privileges in the usual sense. The deputy simply couldn't tell "the authority I hold" apart from "the authority this request is asking me to use." An LLM agent is the most confusable deputy ever built, because its whole job is turning ambiguous language into privileged action.
OWASP catalogs this as LLM06:2025 Excessive Agency, defined as the vulnerability "that enables damaging actions to be performed in response to unexpected, ambiguous or manipulated outputs from an LLM, regardless of what is causing the LLM to malfunction." OWASP splits the root cause three ways, and the split is the right mental model:
Excessive FUNCTIONALITY the agent CAN call tools it never needs Excessive PERMISSIONS a tool acts with more rights than this task needs Excessive AUTONOMY high-impact actions fire with no independent check
Each one maps to a lever you actually control. Take them in order.
Functionality: fewer tools, narrower tools
The first question is not "how do I secure this tool" but "why does the agent have it." An agent that reads calendars does not need send_email. An agent that summarizes documents does not need a shell. OWASP is blunt: prefer granular tools over open-ended ones. A fetch_url(url) that accepts any URL is a server-side request forgery engine wearing a helpful hat. A fetch_internal_doc(doc_id) that resolves IDs against an allowlist is a different animal. The exec(cmd) tool is the canonical mistake, because it collapses your entire threat model down to "can the model be talked into running a command," and the answer is always yes.
Builder: Audit your tool manifest the way you'd audit an IAM policy. Every tool the model can see is attack surface whether or not the task uses it. A read-only agent should not have write tools registered at all. Don't rely on the prompt to keep it polite.
Permissions: bind to the user, not to the service
Here is where most real deployments quietly fail. The agent authenticates to downstream systems with one shared service account, a bot token or database role or API key that holds the union of every permission any user might need. Alice asks a question; the agent queries the database as svc_agent, which can read every tenant's rows. The model, steered by Alice's input (or by an injection hiding in data her request happened to touch), reads Bob's data and returns it. Textbook confused deputy. The deputy held broad authority and couldn't bind it to the requester.
OWASP names the fix directly: execute actions in the security context of the specific user, with the minimum privileges that user needs. In practice, the user's identity has to ride all the way to the downstream system, so its access control does the enforcing rather than a check the LLM is trusted to perform.
CONFUSED DEPUTY SCOPED DEPUTY
user ──req──► agent user ──req + user token──► agent
│ [svc_account: │ [Alice's scoped token,
│ reads ALL tenants] │ reads Alice's tenant only]
▼ ▼
database ──► leaks Bob's rows database ──► Alice's rows; Bob's
DENIED at the DBNotice the enforcement point moved. On the left, the database serves anything svc_agent asks for, so the only thing between Alice and Bob's data is the model's judgment, which attacker text can rewrite. On the right, the database rejects the request no matter what the model "decided," because the token does not carry the authority. That is the whole game: make the credential incapable of the action, so a compromised or confused model cannot perform it. OWASP frames this as putting authorization in downstream systems rather than trusting the LLM to be the gate.
Defender: Treat "the agent holds admin or shared service credentials" as a finding on its own, before you know of any exploit. And log the identity used on every downstream call. If your access logs show svc_agent for actions taken on behalf of thousands of users, you have lost per-user attribution and cannot answer "did this token read data it shouldn't have" after an incident.
Autonomy: independent checks on irreversible actions
Some actions cannot be undone: a payment, a DELETE, an outbound email, a config change. For those, OWASP prescribes human-in-the-loop approval, and the load-bearing detail is that the approval step lives outside the model. Picture an agent that drafts social posts, where the post() tool itself demands human confirmation before it fires. The model can be talked into drafting anything. It cannot be talked into skipping a gate it does not control.
Per-tool credentials and OAuth scopes
Credentials should be as narrow as the tools. If a coding agent needs to read one repository, don't hand it a personal access token carrying the classic repo scope, which grants read and write across every public and private repo, let alone admin:org on top. Reach for a fine-grained token limited to read-only contents on that single repo, or GitHub App permissions scoped the same way. (Classic OAuth scopes barely narrow this at all; public_repo is about as granular as they get.) Scopes turn "the agent's authority" from a blank check into a specific, auditable grant.
A concrete way this bites: in May 2025, Invariant Labs showed an agent connected to GitHub over MCP could be hijacked by a prompt injection planted in a public issue. The agent held a broad token that also reached the user's private repos, followed the injected instructions, and pulled private data (repo details, salary, relocation plans) into a public pull request. Broad token plus attacker-controlled input meant the private data walked out. Invariant is careful to note this is an architectural problem, not a bug in the MCP server code, but least privilege is exactly the architectural control that helps: a token scoped to only the public repo the agent was asked to work on could not reach the private one, so the exfiltration path would not exist at the credential layer.
The pattern has a well-tested precedent outside LLMs. AWS cross-account roles use an external ID specifically to stop a confused-deputy attack, where a shared third-party service could be tricked into assuming a role in the wrong customer's account. Same disease, same cure: bind the authority to a value the attacker cannot supply.
Researcher: Scoping fights blast radius, not the decision itself. A per-user, narrowly-scoped agent can still be injected into misusing the authority it legitimately holds for that user. Least privilege bounds the damage to what this principal could always do; it does not make the model trustworthy. That is why this lesson pairs with prompt-injection defense (Module 7's input-trust boundary lesson) rather than replacing it.
The working checklist
- Register only the tools the job requires; drop open-ended
execandfetch(any_url)for typed, allowlisted operations. - Propagate the end user's identity to every downstream call and enforce authorization there, never in the prompt.
- Retire shared service accounts for user-facing actions; one broad token is one confused deputy.
- Issue per-tool credentials with the narrowest scope that works, read-only when the task is read-only.
- Gate irreversible actions behind an approval step the model cannot invoke on its own.
- Log the acting identity and tool per call; rate-limit and alert on anomalies to bound the damage of the case you missed.
Least privilege does not make an agent safe. It makes an agent's mistakes survivable, which, given that the agent takes its orders from data, is the property you actually need.
Sources
- OWASP LLM06:2025 Excessive Agency — OWASP Gen AI Security Project
- Norm Hardy, "The Confused Deputy (or why capabilities might have been invented)," ACM SIGOPS OSR 22(4), 1988
- Invariant Labs, "GitHub MCP Exploited: Accessing Private Repositories via MCP," May 2025
- AWS IAM docs: The confused deputy problem (cross-account external ID)
- GitHub: Scopes for OAuth apps
- OWASP Top 10 for LLM Applications (2025)