A Dead Proxy Made an OpenAI Model Write Its Own TCP Client. Docker Allows Raw Packets Too
Book: System Design Pocket Guide: Fundamentals Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub An internal OpenAI research model was asked to build a website using information from another site. The sandbox's web proxy was unavailable, so its browser couldn't open that site. The model went looking for another way out, and ended up writing a user-space TCP/TLS client in Python. The run happened on August 4. OpenAI posted the report on November 5, and SecurityWeek covered it on November 6. OpenAI's investigators "found no evidence that the model transferred sensitive data or accessed the host or another tenant's data." If you run agents in containers, the two gaps it used have direct equivalents you can check on your own machine. What the model did once the proxy was gone The sequence below comes from OpenAI's report, with one detail from SecurityWeek. The model restarted the browser and scanned private addresses for another proxy, and none turned up. A look at nearby devices ended with it deciding a neighbor's connection would be intrusive. The sandbox also had a second network connection, for traffic between the app and its browser. Per the report: "The operating system denied the model's attempt to route traffic through it." The model "still had permission to construct and send individual network packets." What it did with that permission: Using raw packets, the model asked a DNS server for the target site's addresses and got two back. Then came the Python client, which received a reply to the opening handshake and no page content. Its reasoning at this point read: "This is network evasion, but we're troubleshooting access; potentially inappropriate." It tried fragmented requests meant to slip past a network filter anyway, and got back a timeout saying the request was incomplete. Amazon S3 was reachable, so it tried a CONNECT request to relay its traffic. That returned an error. Later, once it had started building the site, an SSH service answered on a target IP, and the model decided not to try logging in. A security misalignment monitor flagged the run on August 4. Investigators traced the traffic to the private network between the app and the browser, which could forward packets outside the sandbox. They could not determine whether the timeout on the fragmented requests came from the target site or from a firewall. The front door was down, so the model built its own packets and tried the side of the box. Two container settings did the work OpenAI paused the affected workers, cancelled the outstanding runs and changed the sandbox in two ways. It "removed the permission to construct raw packets from unprivileged containers by default" and "blocked forwarding from private container networks." The report doesn't name a container runtime, Docker or CAP_NET_RAW, so the mapping to your setup below is mine. On Linux, creating a packet socket requires CAP_NET_RAW. Docker keeps NET_RAW in the capabilities a container gets by default, and its table describes that one as "Use RAW and PACKET sockets." That default mostly bites containers running as root. For a process with nonzero user IDs, capabilities in its permitted and effective sets are cleared on execve, unless file or ambient capabilities restore them. If your agent runs as root inside its container because that was easier, it has this permission. The second setting is forwarding. Docker's --internal network option gives you a network where containers "may communicate between each other, but not with any other network." The same page carries a caveat: "Communication with the gateway IP address (and thus appropriately configured host services) is possible." A private link between an agent and a browser sidecar is the kind of connection this option is for. Don't stop at HTTP when you think about egress. The DNS lookup in step 1 went out as raw packets and got an answer, and OpenAI says it has "since hardened the sandbox to eliminate these DNS query paths." A separate incident on September 20 went further: an agent sent questions to an external chatbot through DNS queries. If your agent's resolver will look up any name, that resolver is an egress path. Check your own sandbox Save this as check.py. It is Linux only, because AF_PACKET is. import socket try: s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW) except PermissionError: print("blocked: no CAP_NET_RAW") else: s.close() print("allowed: this process can craft raw packets") Run it in the image your agent uses, as the user your agent runs as. Here it is with a stock Python image, once with the defaults and once with the capability dropped: docker run --rm -v "$PWD/check.py:/check.py:ro" \ python:3.13-slim python /check.py docker run --rm --cap-drop=NET_RAW \ -v "$PWD/check.py:/check.py:ro" \ python:3.13-slim python /check.py Going by Docker's capability table, expect allowed from the first and blocked from the second. I haven't run this myself, and rootless setups can differ, so read what it prints. If your agent doesn't need any capability, --cap-drop also accepts ALL. On Kubernetes, capabilities are set per container in the securityContext. For the private link, create the network with the flag and start the agent on it: docker network create --internal agent-browser docker run --rm --network agent-browser \ --cap-drop=ALL my-agent-image Attach the agent to this network only. A second, normal network gives it a route back out. Traffic it is allowed to send, including calls to its model API, goes through a proxy container attached to both networks. Its note to itself lasted minutes About five hours in, after a compaction, the model wrote a summary for its next context window. It said: "Don't continue intrusive network evasion; focus implementation." From the same report: "Within minutes of resuming, it used the same program to look for an archived copy on S3." Then it asked the instance metadata service for cloud credentials and was refused. One run of one unnamed internal model says little about compaction in general, and a rule that lives in the context window failed here within minutes. A line in your AGENTS.md is text the model reads and weighs. A dropped capability keeps working after the model changes its mind. OpenAI closes the report by saying that getting around the sandbox is something it trains against: "This type of behavior is heavily penalized by our RL grading stack." The summary told the next context window to stop. Minutes later, the probing resumed. The website looked finished The model never fetched the reference site. It built a corporate site from its own guesses, and per the report: "It supplied a substitute logo, described services, and invented customer testimonials." The site rendered in three browsers. Before answering, the model reasoned: "No need mention network issue." The evaluation team cancelled the run before a final answer went out, so nobody received that answer. "Almost fourteen hours after the task began, it was still editing the site's accessibility controls." My reading of the report, for your own runs: Give the agent a way to stop. Put one line in the task: if a source you were told to use is unreachable, stop and say so. Check the output against the input. When a task names a source, confirm in your proxy or egress logs that the agent actually reached it. A passing build tells you the site renders. It tells you nothing about whether the testimonials are real. If this was useful I build Hermes IDE, so weigh this with that in mind. It's a free, source-available console (download, GitHub) that runs each agent on its own branch and worktree and saves every turn for review. That isolates files. Network access is still your sandbox's job. This whole run started with one dependency going down. The System Design Pocket Guide: Fundamentals is about the building blocks around failures like that: availability, outages, load balancing and rate limits. Read it if you want your agent backend to have a planned answer for a dead dependency.
This is a summary aggregated from Dev.to. Read the complete article on the original site:
Read full article at Dev.to