# Agent Access Control: Least-Privilege Scoped Tools

The fastest way to make an agent ungovernable is to give it broad tools. Why least-privilege, scoped tools are the foundation of access control for AI agents.

Access control for AI agents tends to get framed as a policy problem — write the rules, enforce them at runtime. That's half of it. The other half, the half that decides whether the rules can be enforced at all, is a design decision you make before any policy exists: **what tools does the agent get?**

Give an agent a shell, a generic HTTP client, and read/write to the whole filesystem, and no access-control system can save you — because "the agent's permissions" are now "anything those tools can do," which is everything. Give it three narrow, typed tools and you've made access control *possible*: now permissions are a property of the tools, and the tools are a list you control. Least privilege isn't a hardening step you add later. It's the precondition.

## Why broad tools defeat access control

The principle of least privilege is old and uncontroversial: give each component the minimum access it needs, nothing more. For agents it has a sharp, specific consequence, because agent tools vary wildly in how *bounded* they are.

A typed tool has an effect set you can read off its interface. `customer.getProfile(id)` reads one customer. `invoice.send(id)` sends one invoice. You can write a policy about it — this user, this tool, these arguments — and the policy means something.

A broad tool has an effect set that exceeds its interface. `bash(cmd)` is the worst case: one call, unbounded effects. But it's not alone — a generic `http_request(url, body)` can hit your internal network and pivot; a `python_exec` is a shell by another name; an unrestricted `read_file(path)` can read `~/.aws/credentials`. The moment one of these is in the toolset, your access-control system is only as tight as that tool is loose — which is to say, not tight at all. The agent doesn't even have to be adversarial; a wrong turn routes through the broad tool and out the side.

So the first move in agent access control isn't writing policy. It's **removing the broad tools** and replacing them with narrow ones that do the specific jobs.

## What scoped access control looks like once tools are narrow

With a typed tool surface, access control becomes tractable and layered:

- **Per-agent tool allowlist.** Each agent gets exactly the tools its job requires — a research agent gets `web.search` and `doc.read`, not `db.write`. The toolset *is* the first permission boundary.
- **Per-user scope.** The agent acts for a verified end user, and its permissions are a strict subset of that user's. The intern's agent inherits intern access; the admin's agent inherits admin access. Identity flows to every tool call, so "who is this acting for" is always answerable — not a shared service account.
- **Per-argument policy.** Narrow tools have arguments you can gate. `refund.issue(amount)` can be allowed under $50 and escalated above it. You can't write that rule against `bash`.
- **Narrowing across delegation.** When one agent hands off to another, scope and budget only ever *shrink* down the chain — a sub-agent can't acquire a capability its caller didn't have.

Every one of those depends on the tools being narrow enough that a policy about them is meaningful. That's why the toolset comes first.

## The boundary that least privilege can't reach — and what to do with it

Be honest about the limit: some agents genuinely need to run arbitrary code (a coding agent, a data-analysis agent with a Python sandbox). You can't make that surface narrow, so a tool-level access-control layer can't completely mediate it. That's not a failure of the approach — it's a sign you're at the wrong boundary.

For genuinely open-ended execution, the right control is **isolation**: run it in a sandbox where the blast radius is contained and its network egress goes through one door you control. Access control there happens at the *boundary the agent can't route around* (the wire), not at the tool call. Least-privilege tools and a sandbox aren't alternatives — you use narrow typed tools for everything you can, and isolate the irreducibly-broad rest.

## Where to start

1. **List every tool your agent can call. Delete the broad ones.** Replace `shell` / generic-HTTP / whole-filesystem with the specific typed tools the task needs. This single change does more for access control than any policy.
2. **Scope per agent, then per user.** Give each agent only its tools; thread the end user's verified identity so each agent's reach is a subset of that user's.
3. **Gate arguments, narrow delegation.** Put policy on the arguments narrow tools expose, and ensure scope only shrinks across hops.
4. **Sandbox what's irreducibly broad.** If a shell must exist, isolate it; put the control on egress.

Access control for agents is mostly tool design. Get the surface narrow and least-privilege, and policy, [tool-call governance](/blog/how-to-govern-ai-agent-tool-calls), cost attribution, and audit all become achievable. Leave it broad and none of them are. See [what an agentic control plane is](/what-is-an-agentic-control-plane) for the enforcement layer that sits on a narrow surface.

Step 1 — the list of every tool your agent can call — is also the part you don't have to compile by hand. Agents declare their tool surface on every request, so a layer in the call path can show you that list per agent, each tool a switch to allow or deny. For the major coding agents we've already done the enumeration: the [Tool Surface Index](/tool-surfaces) lists what Claude Code and Codex declare, grouped by blast radius. Capturing your own is what [Agentic Control Plane](/) does, one command to install:

<div class="acp-shot-frame">
<img src="/assets/img/screenshots/tool-surface-control-table.png" alt="The tool-surface control table for a live Claude Code agent: every declared tool a row with Allow / Flag / Deny / Approval controls, invoked vs never-invoked status, and a one-click suggested posture — deny 10 never-invoked outward-blast-radius tools" style="width:100%;height:auto;border:1px solid var(--line-2);border-radius:10px;box-shadow:0 20px 50px -24px rgba(0,0,0,0.9);margin:8px 0;" />
</div>

```bash
curl -sf https://agenticcontrolplane.com/install.sh | bash
```
