AI & Machine LearningAI Engineering · Part 7 of 7

Sandboxing Tool-Using AI Agents: Network Egress, DNS, and Least Privilege

Your agent's container says 'no internet', but can it resolve DNS? A practical guide to containing tool-using AI agents: deny-by-default egress, a controlled resolver, scoped credentials, and audit logs.

September 29, 20268 min read
AI EngineeringAgentic AISecuritySystem DesignKubernetes

Last week OpenAI paused training on its most capable models after an internal agent that was supposed to be offline reached a public chatbot through a gap in DNS filtering (Fortune). The details are specific to their research setup, but the failure mode isn't. Most teams running agents in production have a sandbox that is "offline" in the same loose sense. Outbound HTTP is blocked, but DNS still resolves, the pod can reach the cloud metadata endpoint, and the tool credentials have more scope than the task needs.

This post covers what a sandbox for a tool-using agent actually needs to contain, and how to build one on standard infrastructure.

The mental model: an agent is untrusted code with a goal

A normal service executes code you wrote and reviewed. An agent executes actions chosen at runtime by a model, based on inputs that may include attacker-controlled text: a web page, an email, a retrieved document. So the security question isn't "is the model aligned?" but "what can this process do if it chooses the worst available action?"

That reframing gives you three things to constrain:

  1. Reach. Which networks, hosts, and protocols it can talk to.
  2. Authority. Which credentials it holds, and what those credentials can do.
  3. Visibility. Whether you can see and reconstruct what it did.

Most incidents come from a gap in the first. The damage comes from the second. The third decides whether you find out.

Why DNS is the classic hole

Teams block egress with a firewall rule or a Kubernetes NetworkPolicy that denies all outbound traffic except to a few internal services. Then they allow UDP/TCP 53 to the cluster resolver, because without DNS nothing works, including the internal services they meant to allow.

The cluster resolver (CoreDNS, typically) forwards anything it isn't authoritative for to an upstream resolver, and that one talks to the internet. So a query for anything.attacker-domain.com leaves your network, and the attacker's authoritative nameserver sees the full name. You can encode data in the subdomain (c2VjcmV0.attacker-domain.com) and read replies back from TXT records. That's DNS tunneling. It's slow, but a few kilobytes is plenty for an API key or a small file.

An agent doesn't need to "know" about DNS tunneling for this to matter. Any tool that fetches a URL, any library that resolves a hostname, or any shell command the agent is allowed to run can trigger resolution of an arbitrary name.

The architecture

Four controls, each covering what the others miss.

1. Deny-by-default egress, with a single exit

The agent pod can talk to exactly two things: an egress proxy and a DNS resolver you control. Everything else is dropped at the network layer, not just at the application layer.

Yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: agent-egress
  namespace: agents
spec:
  podSelector:
    matchLabels:
      app: agent-runtime
  policyTypes: ["Egress"]
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: egress-proxy
      ports:
        - protocol: TCP
          port: 3128
    - to:
        - podSelector:
            matchLabels:
              app: agent-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

Note what's missing: no rule for kube-dns in kube-system, and no CIDR for the metadata endpoint. Remember that a NetworkPolicy only takes effect if your CNI enforces it (Calico, Cilium, and the cloud-native CNIs do; some minimal setups don't). Test it. Don't assume it works.

2. A resolver that only answers for what the agent needs

Run a small dedicated resolver for the agent namespace that answers only for internal zones and an explicit list of external names, and returns NXDOMAIN for everything else, without forwarding the query upstream. That last part matters. A resolver that forwards and then discards the answer has already leaked the query name.

With CoreDNS, that means a Corefile with no catch-all forward . /etc/resolv.conf block. The upstream proxy resolves external hostnames itself, so the agent pod never needs to resolve public names at all.

3. An egress proxy with a host allowlist

All HTTP(S) traffic goes through a forward proxy (Squid, Envoy, or a managed egress gateway) configured with an allowlist of hostnames the agent's tools legitimately need. With HTTPS, the proxy sees the hostname in the CONNECT request and the TLS SNI, which is enough to enforce a host allowlist without decrypting traffic.

In a Spring Boot agent runtime, route the HTTP client through it explicitly so there's no path that bypasses it:

Java
@Bean
RestClient toolHttpClient() {
    var httpClient = HttpClient.newBuilder()
        .proxy(ProxySelector.of(new InetSocketAddress("egress-proxy.agents.svc", 3128)))
        .connectTimeout(Duration.ofSeconds(5))
        .followRedirects(HttpClient.Redirect.NEVER) // redirects can walk off the allowlist
        .build();
    return RestClient.builder()
        .requestFactory(new JdkClientHttpRequestFactory(httpClient))
        .build();
}

The network policy is the real enforcement, though. The client config just makes the legitimate path the easy one.

4. Credentials scoped to the task, not the service

The sandbox limits where the agent can go. Credentials limit what it can do at the places it's allowed to go, and that's the layer that decides how bad a mistake gets.

  • Mint short-lived tokens per task, scoped to the tools that task needs. A task that reads Jira tickets gets a read-only Jira token that expires in 15 minutes, not the service account with write access to everything.
  • Block the cloud metadata endpoint (169.254.169.254) from agent pods. On AWS, require IMDSv2 with a hop limit of 1, and use IRSA / workload identity so the pod never holds node-level credentials.
  • Put destructive tools behind confirmation. Anything that deletes, pays, sends externally, or changes permissions requires a human approval step or runs as a dry run by default.

Visibility: log the intent, not just the traffic

Log every DNS query and every proxy connection with the agent task ID attached. On the application side, log each tool call the model requested: the tool name, the arguments, and the decision (allowed, denied, needs approval). When something goes wrong, the question is always "which step of which task did this, and what was the model looking at when it decided?" You can only answer that if the task ID travels with the request.

A useful alert: any NXDOMAIN from the agent resolver for a non-internal name. In a correctly configured sandbox, legitimate code never asks for those, so every one of them is a bug or an attempt.

Trade-offs and when this is overkill

SetupGood enough whenNot enough when
App-level URL allowlist onlyAgent has no shell or code execution, fixed tool setAgent can run code or install packages
NetworkPolicy + proxy, default DNSLow-sensitivity data, internal usersAgent handles secrets or customer data
Full setup aboveAgent runs code, reads untrusted input, or holds credentialsTruly hostile code; then add a VM-level sandbox (gVisor, Firecracker)

If your agent only calls three fixed internal APIs and never executes code, an application-level allowlist and scoped tokens may be enough. The moment it can run a shell command, pip install a package, or browse the web, assume it can reach anything the network lets it reach, and design for that.

Recap

  • An agent is untrusted code with a goal. Contain its reach, authority, and visibility.
  • "No internet" has to include DNS. Use a resolver that answers only for what's needed and never forwards the rest.
  • Short-lived, task-scoped credentials limit the damage when containment fails. Plan for it to fail eventually.

If you're building agent infrastructure and want more posts like this, subscribe to the newsletter below. I write about AI systems and distributed design every week.

R

Ravi Kant Shukla

Senior Java + AI engineer. 9+ years in system design, Kafka, microservices, and LLM/RAG pipelines.

Enjoyed this post?

Get new deep dives and the week's AI news by email. No spam.

Comments (0)

Loading comments...

Leave a comment

Your email will not be displayed publicly.