Threat Modeling an Agent's Tool Belt
Everyone wants to threat model the model. Wrong altitude. The model is a text
generator that has never touched your infrastructure. What touched it was the
bash tool you handed over three sprints ago.
An agent’s attack surface is exactly the union of its tools’ capabilities. Nothing more, and — this is the part teams forget — nothing less, including the capabilities that emerge only when two innocuous tools are combined.
Classify every tool on two axes
Before adding a tool, place it on this grid. It takes thirty seconds and it decides everything downstream.
| Reversible | Irreversible | |
|---|---|---|
| Local reach | read a file, run a test | delete a file, git push --force |
| External reach | fetch a public URL | send email, charge a card, deploy |
The top-left quadrant can run unattended. The bottom-right needs a human, every time, no exceptions, no “remember my choice for this session.”
The dangerous quadrant is top-right: reversible but externally reaching. A
fetch tool looks harmless — it only reads, after all — until you notice that
the URL it reads is attacker-chosen and the query string can carry whatever the
model just learned. Any tool that makes an outbound request is an exfiltration
tool. Classify it that way.
Composition creates capabilities nobody granted
Tools are not independent. Reason about them as a set:
read_file+http_fetch= arbitrary local file exfiltration.list_env+ anything outbound = credential theft.write_filescoped to the repo + a CI pipeline on push = remote code execution, on a delay.
That last one is worth sitting with. An agent restricted to editing files in one repository has, transitively, the privileges of your build system — which, per the CI post, holds every secret you own. “It can only edit files” is not a boundary if something downstream executes those files.
Sandbox at the OS, not in the prompt
Only modify files under /workspace is a wish. Make it a property of the
environment instead: run the agent in a container with the workspace as its only
writable mount, no host network, and a proxy that enforces an egress allow-list.
# The parts that actually constrain the agent.
services:
agent:
image: agent-runtime:pinned-sha
read_only: true # rootfs immutable
tmpfs: [/tmp]
cap_drop: [ALL]
security_opt: [no-new-privileges:true]
volumes:
- ./workspace:/workspace:rw # the only writable path
networks: [egress-proxy] # no direct internet
environment:
HTTPS_PROXY: http://egress-proxy:3128
Everything the agent can reach is now enumerable by reading a config file, not by reasoning about what a model might decide to do. That is the difference between a control and a preference.
Third-party tool servers are dependencies
An MCP server you install is code running with your agent’s privileges, holding whatever token you configured it with. It deserves the same scrutiny as any other dependency, plus one concern that is specific to this ecosystem: the tool descriptions themselves enter the model’s context. A malicious server can put instructions in a tool’s description field, and the agent reads them as authoritative.
Before wiring one up:
- Pin the version. A tool definition that changes silently is an injection vector with a delivery mechanism.
- Read the tool descriptions, not just the README. That text is prompt input.
- Give it a scoped token. A GitHub MCP server that only needs issues should not
hold a PAT with
repoandworkflow. - Assume anything it returns is untrusted content, because it is.
Log the tool calls, not the conversation
When something goes wrong, the transcript tells you what the agent said. The tool call log tells you what it did. Record, for every invocation: the tool, the full arguments, the caller identity, the timestamp, and whether a human approved it.
That log is your incident timeline, your abuse detector, and — the underrated one — your least-privilege audit. Nine tools granted and three ever called is a question worth asking at the end of every month.
Give the agent the smallest tool belt that lets it finish the job, make the irreversible entries ask permission, and put a wall around the rest that does not depend on anyone being persuasive.