DEV Community

Cover image for Your AI agent sandbox has no gate
Andrea
Andrea

Posted on

Your AI agent sandbox has no gate

Put an AI agent in a Docker container. Lock the network down. Mount only the directories it needs. The agent can't escape. Good. But now it needs to actually do something — read files on the host, call a REST API, push to GitHub, query a database. The container has two options: block everything, or let everything through. There's no middle ground. No "you can read this file but not that one." No "you can call this API but not send the response to pastebin.com." The container is a wall with no gate.

This is the gap nobody talks about. Sandbox providers — E2B, Docker, Fly.io, Firecracker — have done good work on isolation. But isolation is binary: in or out. The moment your agent needs to interact with anything outside the container, you either punch a hole with no controls, or you lock it down so hard it can't do its job.

What you actually need is a controlled exit. One door. With a guard that checks every request before it goes through, and logs everything.

We've been building SentinelGate — an open-source MCP proxy — for the past few months, and container integration was something we thought about early. Not because someone asked for it. Because the architecture is a natural fit: put SentinelGate inside the container as the only way out. The agent can't reach the host filesystem, GitHub, or any external API directly — every MCP tool call has to go through SentinelGate first. The container removes all other exits. SentinelGate controls the one that's left.

What sandboxes don't do

This isn't a criticism — it's a category distinction. Sandboxes are infrastructure-level tools. They manage network boundaries, filesystem mounts, process isolation, resource limits. They're good at it. But they operate at the perimeter, not at the gate.

A sandbox can block all outbound traffic. Or allow it. It can't say "this tool call to the host filesystem is a read, so it's fine, but that one is a delete, so block it." As far as Docker is concerned, both are just traffic on a socket. It can't scan the content of a request for PII or credentials before it leaves. It can't require human approval for a destructive operation while letting reads flow through. And it can't notice that an agent which normally makes 90% read calls has suddenly shifted to 60% writes.

These aren't sandbox failures. They're a different layer. The sandbox controls the perimeter. The gate controls what crosses it. You need both, and pretending one covers the other is how things go wrong.

Making it actually work inside containers

I knew SentinelGate made sense inside containers. The architecture is a natural fit: proxy sits between agent and tools, container provides the boundary, done. But knowing something makes sense and getting people to actually do it are different problems.

Before bootstrap existed, using SentinelGate in a container meant configuring each one individually. Create the identity. Set up the API key. Write the policies. Connect the upstream. If you already knew SentinelGate well, that was maybe 20 minutes. But the real issue wasn't the time — it was that the whole approach didn't fit how containers work. Containers get created and destroyed constantly. Some live for a minute. You're not going to sit there and manually configure a security proxy for a container that's gone before you've finished typing. The configuration model was structurally wrong for the environment.

So I rebuilt it around a single command. Bootstrap takes a JSON payload with everything — identities, policies, upstream MCP servers, security profile — and configures SentinelGate in one shot. The container starts, bootstrap runs, protection is active. When the container dies, there's nothing to tear down.

curl -X POST http://localhost:8080/admin/api/v1/bootstrap \
  -H "Content-Type: application/json" \
  -d '{
    "profile": "strict",
    "upstreams": [{"command": "npx", "args": ["@modelcontextprotocol/server-filesystem", "/workspace"]}],
    "identities": [{"name": "agent-1", "roles": ["agent"]}]
  }'
Enter fullscreen mode Exit fullscreen mode

The profile field picks one of three presets. Strict denies everything by default, enables content scanning, and requires human approval for critical operations. Standard blocks destructive operations but allows reads and most other calls. Permissive allows everything and logs it all. The idea is you start strict and relax as you understand what the agent actually needs — you don't have to write a single CEL expression on day one.

In container environments, there's a timing problem that most people don't think about until it bites them. The process starts, but is it ready? For SentinelGate, "ready" doesn't mean the HTTP server is listening. It means bootstrap is complete, policies are loaded, upstream connections are established, and the proxy is actively enforcing. The /readyz endpoint returns 200 only after all of that. Your orchestrator checks it, gets a 200, and knows there's no gap between container start and active protection.

# In your Dockerfile or orchestrator health check
curl -f http://localhost:8080/readyz
Enter fullscreen mode Exit fullscreen mode

Then there's the kill switch. One API call stops all agents immediately. One call resumes. It sounds simple because it is, but it's the kind of thing you're very glad exists at 2am when an agent is doing something you don't understand and you need everything to stop now, not after you've figured out which policy to update.

The resource footprint matters too, because nobody wants a heavy sidecar eating into their container's allocation. SentinelGate is a single Go binary — no runtime, no dependencies. Around 50MB of RAM. Sub-millisecond latency added per tool call.

What it doesn't do

Same honesty we put in the README, because we'd rather you know upfront: SentinelGate is an MCP proxy. It intercepts what goes through MCP. If an agent makes a raw syscall, runs a curl that doesn't route through any MCP server, or uses a native file operation that bypasses the protocol entirely — SentinelGate doesn't see it. It can't. It's not in that path.

This is exactly why the two layers are complementary, not competitive. The container removes all uncontrolled exits — no raw network access, no host filesystem, no escape. SentinelGate is the one exit you leave open, and it decides what gets through. Remove either one and you've got a gap. The container without a gate is a wall you have to tear open every time the agent needs something. The gate without a container is a door the agent can walk around.

Try it

curl -sSfL https://raw.githubusercontent.com/Sentinel-Gate/Sentinelgate/main/install.sh | sh
sentinel-gate start
Enter fullscreen mode Exit fullscreen mode

Point your agent at localhost:8080/mcp, open the admin UI at localhost:8080/admin, and set up a deny rule. The full integration guide for containers is in the docs.

GitHub: github.com/Sentinel-Gate/Sentinelgate
Site: sentinelgate.co.uk

If something doesn't make sense or doesn't work, tell us.

Top comments (0)