When I made project-sandbox, the network isolation piece was rather simplistic: Block DNS and generic egress, look up allowed domains in advance, and allowlist their IPs, providing those IPs inside /etc/hosts. For a small allowlist and LLM APIs this works, but things get more challenging when the domains are behind a CDN, or if the actual destination only gets determined inside the TLS tunnel.

Here is a write-up of implementing a better filtering approach in internet-proxy-locally and some notes on how to use coding agents to get there.

Why filter? #

I wanted an allowlisting proxy for internet traffic to accompany the agent gateway setup.

This has a few advantages when working with untrusted code: compromised packages that install remote controls, libraries that phone home, or just accidental agentic pushes (or intentional sharing after prompt injection) - a lot of these become more difficult with a proxy that bounds traffic.

The proxy rabbit-hole #

I did some research on what the best such proxy would be. The first options that came up were Pipelock, Smokescreen, Squid, and iron-proxy, and to compare them I ran a small benchmarking exercise on filtering and behaviour.

The four proxies implement different choices and setups for security - Squid is a generic caching proxy that can be configured for security, while the other ones target more security-oriented scenarios, from protecting enterprise networks (Smokescreen) to specific controls for agent internet traffic (iron-proxy and Pipelock). For Squid, the policy is in a config file, whereas the other ones encode security settings in their code. Apart from allowlisting, most of them implement some sort of SSRF protection - and protection of cloud metadata endpoints (which will start to matter when deploying any of these images outside a local container).

The full results are in findings.md. The labs can be reproduced, and the repo doubles as a filtering proxy setup for project-sandbox. The default solution internet-proxy-locally runs for now is Pipelock - but there likely is some work left to add benchmarks & config hardening.

The interesting differences appear when looking at TLS inspection - e.g. SNI mismatches (which could allow traffic to bypass allowlists) and general handling of TLS tunnelling.

Why so much code? #

The repo started out as a Python script that would start a container, and ended up with quite a number of Python files. This, to an extent, is the result of working with coding agents: They love creating abstractions and will also produce a lot of tests - essentially for everything they cannot directly test, or which they believe should be protected against regressions.

I did a bit of cleanup to be somewhat more deliberate about the implementation choices (Opus constructed an elaborate version pinning & config mechanism that I thought wasn't all that useful), but here are some of the things the agents did that I liked and kept:

  • The design of the egress tests - this was refactored from a single Python file that encoded quite a few proxy-specific assumptions.
  • Testing in a sandbox gets pretty elaborate. When coding agents cannot test directly because they're in a sandbox (no access to docker / container), they will come up with fairly complex mock scenarios. Here, Claude created a mock proxy that can be used to emulate the expected behaviours.
  • Being precise about the reason for denial. If it's not clear that the policy is the reason something was denied, analysis of the logs is needed. One extra learning from this: There can be a minor clock difference (100s of ms) between inside and outside the container.

A few things to watch out for with agents (specifically Opus 5, which was one of the major contributors, but also Astra 6 to some degree):

  • They really like to comment verbosely, and often in redundant places.
  • They follow the structure of the repo - and when left alone with long and complex tasks, that becomes somewhat self-reinforcing and increases complexity of the generated code. I guess it's similar to an individual developer who is very productive and codes more than they design.
  • Tests within tests. When designing the host-side end-to-end checks, the agents would create tests for these checks. So, reading the tests is important, and pruning is needed.