Control agent access
How to sandbox an AI agent and control its network egress
Run the agent and its local servers in a container that holds only what the task needs, and let it connect only to a short list of addresses.
New to MCP? What is an MCP server? covers the basics first.
A model that reads text can be tricked by text. A sandbox doesn't try to prevent that. It limits what a tricked agent can reach.
The risk on your own machine
A local MCP server is a program your AI app starts for you, often from an npx or uvx line copied out of a README. The MCP spec's security guidance is blunt about it: a malicious server gets "arbitrary code execution" with your app's privileges, and you have "no visibility" into what ran. A legitimate server that turns bad in an update is just as dangerous.
Agents that run shell commands or scripts carry the same risk. In both cases the question is what the process can reach if it does something you didn't ask for.
Files and credentials
The spec's advice is to "launch MCP servers with restricted access to the file system, network, and other system resources" using containers or similar sandboxes.
Mount only the directory the task needs, read-only unless it writes. A code-review agent needs the one repository under review, not your home directory. A container enforces that whatever the agent decides. A prompt saying "don't read other folders" doesn't, because a model can be talked out of an instruction.
Then remove ambient credentials. These are secrets a process picks up just by running where it runs, such as cloud keys in ~/.aws/credentials. If the sandbox doesn't have them, an injected instruction can't steal them. Give the task only the credentials it needs, attached at call time, as covered in keeping API keys out of your agent's reach.
Why outbound limits work
Simon Willison's lethal trifecta is an agent with private data, exposure to text an attacker wrote, and a way to send data out. Take away any one and the attack fails. Limiting outbound connections takes away the third without having to spot the attack.
That third leg is wider than it looks. An agent doesn't need a send_email tool to leak data. A link in its answer, or a markdown image some clients load automatically, makes a request too. Reading through the tool list won't find these.
An allowlist does. If the sandbox can only reach api.github.com, an instruction to post to attacker.example has nowhere to go. It's a backstop, though. If the one allowed destination is also where the attacker wants the data, the allowlist won't help, which is why the data the agent can see needs narrowing too.
Block internal addresses
Allowlists cover the internet. Your own network needs a separate block, applied by default:
- Private IPv4:
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 - Loopback:
127.0.0.0/8,::1 - Link-local:
169.254.0.0/16, which includes cloud metadata endpoints - Private IPv6:
fc00::/7,fe80::/10
The link-local range matters most. 169.254.169.254 is the metadata address on AWS, GCP and Azure, and it can hand back the credentials of whatever role the machine runs as. An agent with no keys of its own can still get some by asking it.
Check on every connection, too. With DNS rebinding, a hostname resolves to a safe address when you check it and an internal one when the request goes out. For server deployments the spec recommends routing traffic through "an egress proxy that enforces network policies."
Setting it up
- Put local servers and code-running tools in containers.
- Mount only the directory the task needs, read-only by default.
- Remove ambient credentials and attach what the task needs at call time.
- Block private, loopback and metadata addresses.
- Allow only the specific destinations the task needs, and log what gets blocked.
In Statio
Statio does this for the servers it hosts. They have no direct route to the internet. Outbound traffic goes through Statio's egress proxy, which checks each destination against that server's allowlist. Nothing is reachable until it's added, only port 443 is open by default, a bare wildcard is refused, and blocked attempts are logged. The gateway also refuses to call anything that resolves to a private, loopback or cloud metadata address, and connects to the exact address it checked, so a DNS change between the check and the call doesn't get through. Hosted servers run under a service account with no other permissions.
A server you run on your own machine is outside Statio's reach, so the container and filesystem steps above are yours to do. The MCP security checklist asks about exactly this.