Remove adsMember
Theme: SystemContrast: Standard
On this page
  1. Agents need this more than people do
  2. The lethal trifecta
  3. Fence the doors, not the room
  4. MCP servers are doors too
  5. Reviewable agents
  6. Rolling it out

Least privilege for AI agents and MCP servers, in practice

By 8 min readIntermediate
Copy article
Share this article

Friday afternoon. A migration is half done and the agent keeps stopping to ask “may I run this?”. So you do what every tired engineer does: hand it a token that can do anything in every repository, and go get coffee.

The agent is polite and competent and does nothing wrong. Then on Monday it reads an issue comment a stranger wrote and follows the instructions in it, because to a language model, text is text [4]. The model didn’t fail. The token was the mistake.

An agent is a process acting on your behalf, so treat it like one: give it the minimum the task needs [8], and enforce that outside the model, because you can’t make a model reliably refuse [3][4]. Done well, this doesn’t slow the agent down. It cuts the prompts, and it gives your team a log someone can actually read [1][6][7].

Agents need this more than people do

NIST’s definition of least privilege covers “users (or processes acting on behalf of users)”, limited to the minimum their tasks need [8]. That’s an agent exactly: a process acting for someone, with a habit of reading text from places you didn’t choose.

OWASP calls the failure mode excessive agency: a system that takes damaging actions in response to unexpected, ambiguous or manipulated output, whatever caused it. It might be a hallucination, or an injected prompt, including one that arrives through a compromised extension [3]. OWASP gives three root causes: too much functionality, too many permissions, too much autonomy [3].

For the injection half, Willison’s advice is blunt. Filters that catch most attacks aren’t good enough, because in web security 95% is a failing grade. Avoid the dangerous combination instead [4].

So don’t ask how to make the model behave. Ask what the worst thing is that a misbehaving model could do with exactly these keys.

The lethal trifecta

Willison names three capabilities that are harmless alone and dangerous together [4]:

Capability Typical example A way to cut it
Access to private data Repo, mailbox, .env files Read deny rules, a narrower working folder
Exposure to untrusted content Web pages, issues, PRs, inbound email A separate agent session for the untrusted input
A way to send data out HTTP, git push, outbound email Network allowlist, approval for sends

Put all three in one agent session and a hidden instruction in the untrusted text can fetch the private data and send it out. OWASP’s own example is a mail assistant whose plugin can both read and send; a malicious email tells it to forward the whole inbox. Its fixes: a read-only scope, and a person approving every outgoing message [3]. You only need to remove one leg.

There’s a catch for MCP, and Willison points it out. Vendors can patch their own products, but they can’t protect you when you combine tools from different sources, and combining tools from different sources is what MCP is for [4].

The lethal trifecta: private data, untrusted content, a way out

Fence the doors, not the room

“If I fence it in, it stops being useful.” Not if you fence the doors instead of the room. Inside the project folder the agent can read, edit, build and test as freely as you can. The boundary sits where things leave that folder.

Anthropic’s engineering team argues this from the productivity side. Asking approval for most actions causes approval fatigue: people stop reading what they approve. Their sandbox isolates the filesystem and the network together, because either alone leaks. Without network isolation, a compromised agent could send out your SSH keys. Without filesystem isolation, it could slip out of the sandbox and reach the network anyway. Internally, it cut permission prompts by 84% [6]. Fewer prompts and tighter walls, at the same time.

An agent working freely inside a sandbox, with narrow, scoped doors

Give freely Gate behind a prompt or a second token Never give
Read and edit inside the repo git push, deploy, anything that sends Cloud admin and org-wide tokens
Run tests and the build Writes through an MCP server Reading ~/.ssh, .env, secrets folders
Read-only MCP tools Installing a new MCP server Unrestricted outbound network

Claude Code’s rules show what this looks like, and any agent with a permission file can do the same. Rules are checked deny first, then ask, then allow, and the first match wins, so a narrow allow can’t undo a broad deny [5]:

json
{
  "permissions": {
    "allow": ["Bash(npm run *)", "Bash(git diff *)", "mcp__github__get_*"],
    "ask": ["Bash(git push *)"],
    "deny": ["Read(./.env)", "Read(./secrets/**)", "Bash(curl *)", "Bash(wget *)"]
  }
}

mcp__github__get_* allows only the read-style tools of one named server. MCP allow rules need a literal server name before any wildcard [5]. And the permission file can live in version control, shared with the whole team [5].

Don’t mistake these rules for a wall, though. They’re a speed limit. Denying Bash(curl *) stops curl ... but not /usr/bin/curl or sh -c 'curl ...', and a Read deny doesn’t cover a Python script that opens the file itself. For limits that have to hold, the docs point to the OS-level sandbox [5].

MCP servers are doors too

Every MCP server is a door, and the spec is frank about what happens when doors are sized for convenience.

A local MCP server is code on your machine, running with your privileges. A malicious start-up command in a client configuration can do anything the client can. So clients must show the exact command before a one-click setup runs it, and should run servers sandboxed, with minimal privileges by default [1].

Broad scopes make every leak bigger. The spec describes a token with scopes like files:*, db:* or admin:*, granted up front because the server advertised everything. Leak it and the attacker can move sideways; revoke it and every workflow breaks. And one omnibus scope hides what each individual operation was meant to do, which matters for auditing [1]. The spec recommends a progressive model instead: a minimal starting scope for low-risk reads, step-up through a targeted WWW-Authenticate challenge the first time a privileged operation is attempted, and each elevation logged with a correlation ID [1].

A token has to be meant for the server that receives it. An MCP server must not accept tokens that weren’t issued to it, and passing a client’s token through to a downstream API is explicitly forbidden. It sidesteps rate limits and monitoring, and the logs show the wrong identity [1].

The authorization spec asks clients to request only the scopes needed for the operation at hand, and servers to name the required scopes in the WWW-Authenticate challenge [2]. If you write a server, keep scopes_supported small and split read from write. If you only use servers, start with a read-only token, and if a server asks for “everything”, look for another server.

Reviewable agents

Least privilege usually gets sold as defence. For a team, I think the bigger win is that it makes the agent reviewable.

Question a reviewer or auditor asks Where the answer lives
What could the agent do? Permission rules and token scopes, in the repo [5]
Who decided that? A pull request on the rules file, reviewable like any config
What did it do? Tool-call events with the tool name, success and duration [7]
Who allowed this call? The decision source: config, hook, or a person [7]
Whose authority did it use? A token issued to the specific server, not passed through [1]

Claude Code, for example, exports OpenTelemetry events that record each tool accept or reject along with where the decision came from, plus a result event per tool [7]. Content is redacted by default. Prompts and tool parameters such as commands and file paths stay out of the logs unless you turn on OTEL_LOG_USER_PROMPTS or OTEL_LOG_TOOL_DETAILS, and a repository can’t switch on telemetry or content logging for you [7]. Think before you enable them: command lines can contain secrets, and an audit log full of secrets is a new problem.

Admins can go further. Managed settings sit above user and project settings, and allowManagedPermissionRulesOnly makes them the only source of permission rules, which suits regulated teams [5].

For QA it’s simple: the agent’s boundary becomes a diff. A new allow line shows up in review, someone asks why, and the answer gets written down. Repeated prompts or denials in the logs are a free to-do list. Each one is either a rule worth adding or a boundary worth keeping.

Here’s a five-minute exercise. Open your agent’s permission file and one MCP token you use. For each, write down the worst thing it could do if the next text it reads contains a stranger’s instructions. Any answer that makes you wince is your first rule.

Rolling it out

Start with an inventory: every agent and MCP server in use, and what each credential can reach. Then fence the data, by denying reads of secrets and credential folders while leaving the rest of the repo open. Close the way out next. Turn on the sandbox or an outbound allowlist before anyone starts debating individual commands.

Give each server its own token, read-only to start, with step-up and a person approving for writes. Keep the rules in the repo and review changes like code, with managed settings if you need to enforce them. Turn on decision logging with content off, and only add command details where there’s a retention policy. And re-run the trifecta check every time someone adds a server.

None of this fixes prompt injection itself. Nobody has a complete fix; the point is to limit what a successful one can do [4]. Pattern rules can be bypassed, so keep a sandbox under them [5]. And the standards are still moving. The MCP spec has draft and dated versions, so check which one your client and server implement [1][2]. OWASP also publishes a separate Top 10 for Agentic Applications (December 2025) covering tool misuse and privilege abuse. I haven’t summarised it here; read it directly [9].

Share this article

Share this article

Sources

  1. Model Context Protocol: Security Best Practices (2026-07-28)1234567
  2. Model Context Protocol: Authorization (2026-07-28)12
  3. OWASP GenAI: LLM06:2025 Excessive Agency1234
  4. Simon Willison: The lethal trifecta for AI agents123456
  5. Claude Code docs: Configure permissions1234567
  6. Anthropic Engineering: Claude Code sandboxing12
  7. Claude Code docs: Monitoring usage (OpenTelemetry)12345
  8. NIST CSRC glossary: least privilege12
  9. OWASP GenAI: Top 10 for Agentic Applications for 20261

Keyboard shortcuts

Close

Everywhere

Search tools and actions
Ctrl K
Show this help
?
Switch theme
Ctrl Shift L
Pin the selected tool (in search)
Ctrl P

On a tool

Send the output to the first suggestion
Ctrl ↵
Open a file into the input
Ctrl O
Copy the output
Ctrl Shift C
Close searchesc
toolsBase64 encoder and decoderEncode text to Base64 or decode Base64 back to text, with standard and URL-safe alphabets.EncodingJSON formatter and validatorFormat, minify and validate JSON, with the exact line and column of any error.FormattingRegex testerTest JavaScript regular expressions with live highlighting, capture groups and a plain-language explanation.TextUUID and ULID generatorGenerate UUID v4, UUID v7 and ULID in bulk, or paste an ID to see its version, variant and timestamp.GeneratorsUnix timestamp converterConvert Unix timestamps to dates and back, in any time zone, with ISO 8601, RFC 2822 and relative time.Date and timeHash and HMAC generatorCalculate MD5, SHA-1, SHA-2, SHA-3, BLAKE2, BLAKE3 and CRC-32 hashes of text or a file, with optional HMAC.SecurityPassword generator and strength checkerGenerate random passwords, passphrases and PINs, or check how strong a password is.SecurityJWT decoder and verifierDecode a JSON Web Token, check its expiry and claims, and verify its signature with a secret or public key.SecurityCIDR and IP subnet calculatorCalculate the range, mask and size of IPv4 and IPv6 networks, split them into subnets, and check what contains or overlaps what.NetworkCron expression explainerRead a cron expression in plain language, see its next runs in any time zone, and tidy it into its standard form.Date and timeSort linesSort lines of text A to Z or Z to A, in natural order (file2 before file10), by code point, by number or by length.TextRemove duplicate linesRemove duplicate lines from a list, keep the first or last copy, ignore case or spaces, or drop every line that repeats.TextCompare lists and textCompare two lists line by line: find what is in both, only in one, or in either, or see a marked line diff side by side.TextText case converter and slugifyConvert text to camelCase, snake_case, kebab-case, Title Case and more, or turn a title into a URL slug with accents handled.TextURL encode, decode and parsePercent-encode and decode text for URLs, or take a URL apart into scheme, host, path and query and edit the query as a table.EncodingValidators: IBAN, BSN, VAT, card and ISBNCheck an IBAN, Dutch BSN, credit card number, EU VAT number, email address, URL or ISBN, with the reason when it fails. One value per line.ValidationHTML entity encoder and decoderEscape text for HTML with named, decimal or hex entities, or turn entities such as & and é back into characters.EncodingEscape and unescape stringsEscape text for a JSON or JavaScript string, a SQL string, a CSV field, a regular expression or a shell word, or undo the escaping.EncodingEAN, UPC and GTIN check digitsCheck the check digit of an EAN-8, EAN-13, UPC-A or GTIN-14, add a missing one, read the country from the GS1 prefix and convert between ISBN-10 and EAN-13. One code per line.ValidationTest data generatorGenerate valid-looking test IBANs, BSNs, EAN barcodes and EU VAT numbers in bulk. They pass the checks, but belong to no one.GeneratorsSQL formatter and minifierFormat SQL so each clause sits on its own line, or minify it onto one line, for MySQL, PostgreSQL, SQLite, SQL Server and BigQuery.FormattingNumber base converterConvert numbers between binary, octal, decimal, hex and any base from 2 to 36, with big numbers, two's complement and a bit view.EncodingYAML, TOML and JSON converterConvert between JSON, YAML and TOML, or validate and pretty-print a document, with the line and column of every error.FormattingCSV and JSON converterConvert CSV to a JSON array of objects or arrays, or JSON back to CSV. Detects the delimiter and reads Dutch Excel files with semicolons and decimal commas.DataGlob and .gitignore testerTest glob patterns and .gitignore rules against a list of paths and see which paths match and which rule matched them.TextColour converterConvert a colour between HEX, RGB, HSL, HWB and OKLCH, check its WCAG contrast against a background, or build a Material 3 tonal palette from it.Formattingchmod calculatorConvert Unix file permissions between octal (755), letters (rwxr-xr-x), ls -l and chmod clauses, with a checkbox grid and the setuid, setgid and sticky bits.SecurityUser-agent parserRead a user-agent string: browser, engine, OS, device type and whether it is a bot. Leave it empty to read your own browser.NetworkConvert cURL to codePaste a cURL command and get the same request as fetch, Axios, Python requests, Go or PHP. Nothing is run and nothing is sent.NetworkMarkdown previewWrite Markdown and see it rendered as you type, or copy the HTML. Raw HTML is shown as text and links are checked, so nothing in the preview can run.FormattingBarcode and QR code generatorMake a QR code, EAN-13, UPC-A, Code 128, ITF-14 or Data Matrix barcode, download it as SVG or PNG, or read a code from an image.GeneratorspagesChangelogWhat changed in each releasepagesAboutWhat codeknife.dev is and who makes itpagesIdeasSuggest improvements and vote on other visitors’ ideas.pagesPrivacyWhat leaves your browser and what is storedpagesAccessibilityConformance, known limitations and how to report a problempagesMembershipRemove ads and raise the limitspagesactions