Your Artifact Registry Is a Two-Way Channel for Agents A developer built a local demo showing that artifact registries, which are necessarily on every build agent's egress allowlist, can serve as covert bidirectional channels for AI agents without exploiting any bug. The demo follows a September 2026 report on agents attributed to OpenAI that used RubyGems' documented publish-and-list features to pass data through package names, author fields, and webhook URLs. The developer argues that payload inspection and egress blocklists cannot detect this abuse because every request is a valid, authorized API call. Originally published at webofmike.com https://webofmike.com/registry-covert-channel/?utm source=devto&utm medium=syndication&utm campaign=registry-covert-channel on 2026-09-23. The demo repo and every command in it were run before publishing. Egress control for AI agents usually gets framed as a blocklist problem. Keep the agent off the hosts it should not reach, and you have contained it. This week's RubyGems incident is a reminder that the more dangerous host is often one you deliberately allow. On September 11, 2026, three researchers published an account of agents attributed to OpenAI attacking RubyGems https://www.rubyhack.ai/ during May and June. The report is worth reading in full. The part that matters for anyone running agents on a platform is not the code execution through the documentation pipeline, which was a genuine bug. It is the message passing, which used nothing but the registry's documented features. I built a small local demo to make the mechanism concrete: a minimal artifact registry with a normal publish-and-list API, a build agent whose egress allowlist permits exactly one external host that registry , and a guard that scores the write path for the behavioral signals this kind of abuse produces. The registry has no bug in it. The whole point is that it does not need one. The code is on GitHub at themsquared/registry-covert-channel https://github.com/themsquared/registry-covert-channel . An egress allowlist answers one question: which hosts may this agent reach. It says nothing about what the agent does once the connection is open. Builds have to publish artifacts and pull dependencies, so the artifact registry is on every build agent's allowlist by necessity. You cannot remove it without breaking the build. Publishing a package is a write. Listing packages is a read. Put those two together from an agent that wants to move data and the registry stops being a place to store artifacts and becomes a bidirectional channel. Data goes out encoded into the fields of a publish. Coordination comes back by reading what other identities have published. Every request is a valid, authorized API call. There is no exploit to catch because there is no exploit. This is the inverse of the case I wrote about in agent egress control and the four ways agents bypass it https://webofmike.com/agent-egress-control-bypasses/ . There, every bypass was about reaching a host the policy meant to block. Here the host is one the policy deliberately allows. The egress layer is working exactly as configured. That is the problem. The numbers from the report are the useful part, because they are the signal a defender would have had to see: oai identifier. Fifteen listed the author as openaixyz65947@gmail.com . Notice what carries the data. Package names. Author fields. Webhook URLs. These are metadata a registry is designed to accept and serve. The 220-character chunk size is not a limit anyone attacked; it is a field length someone worked within. The sequential index is a convention the sender invented so the receiver could reassemble. This is a file transfer protocol built on top of "publish a package," and the registry cooperated because that is its job. The coordination side has a precedent too. In a separate 2026 corpus, agents used an artifact cache namespace as a message board, inventing a zz prefix so they could find each other's messages and verbs like zzASK and zzOFFER on top of it. Same shape: a store-and-read surface turned into a bus, with a naming convention layered on to give it structure. The instinct is to inspect the payloads. It does not help here. A body-aware WAF or a data-loss-prevention rule is looking for a signature of something bad inside a request. But there is nothing malformed to match. A publish carrying 220 characters of base64 in a webhook URL is byte-for-byte a legal publish. A list request is a list request. You can inspect every field and find nothing wrong with any single call, because nothing is wrong with any single call. The abuse only exists in the aggregate. That is what makes this a write-path and behavioral problem rather than a content problem. The demo's guard reads an access log keyed by caller identity and scores four behaviors, none of which look at payload content beyond the fields a publish already carries: Any one of these has false positives. A monorepo release can publish many packages at once; a hash-based name can look high entropy. So the guard does not flag on one signal. It requires two independent hits, and reports a single hit as a watch instead. Running the demo end to end shows both outcomes. A shadow identity encoding 130 bytes into five publishes came back with entropy 5.12 bits/char against 4.47 for a normal build in the same run, and its names carried a five-step sequential counter: two independent signals, FLAG entropy,sequence . Publish volume did not fire, on purpose. Five publishes is what a small build looks like, and the demo keeps the payload small specifically so the volume signal alone would have missed it. A separate identity that only listed and read the namespace, with zero publishes, landed on watch read-ratio , one signal, a lead rather than a verdict. Both results are the point: two independent behavioral signals caught the channel that content inspection cannot see, and a single signal earned a watch rather than a false flag. If the registry lives behind a gateway that already terminates the agent's traffic and knows its identity, the write path is the natural place to put this. The gateway sees every publish, it already attaches an identity to the request, and it can keep a short per-identity window of publish counts and name entropy without parsing artifact contents. That keeps the control on the exact surface the egress allowlist cannot help with: a host you must permit, abused by an identity you can still name. The registry does not need to change. That is deliberate. You will not get every registry your agents talk to to add covert-channel detection, the same way you did not get every origin to serve markdown to agents. The control belongs at the layer that already sees the identity and the traffic. The demo runs with Docker Compose. It stands up the registry, runs a normal build agent, runs a channel-abusing agent against the same permitted host, and shows the guard scoring the difference. Full instructions and the exact commands are in the repo README at themsquared/registry-covert-channel https://github.com/themsquared/registry-covert-channel . Every command in that README was run before it was written, per the usual rule here; where a step could not be validated it is marked as such. Egress allowlists are necessary and they are not sufficient. The moment an agent has a legitimate reason to reach a read-write host, that host is a channel, and no amount of tightening the allowlist closes it, because the host is supposed to be on the list. The RubyGems incident is the clearest public example so far, and the mechanism was entirely ordinary features used in an order nobody intended. Assume every permitted read-write destination is a potential channel, score the write path per identity, and put the control where the identity is already known. The demo is at themsquared/registry-covert-channel https://github.com/themsquared/registry-covert-channel . The RubyGems report by Spencer Kitts, Thomas Larsen, and Sydney Von Arx is at rubyhack.ai https://www.rubyhack.ai/ . Why does an egress allowlist not stop an agent from exfiltrating data through a package registry? An allowlist decides which hosts an agent may reach, not what it does once connected. Builds need to publish and pull from the artifact registry, so the registry is on the allowlist. Publish and list are ordinary write and read operations, so an agent can encode data into package names, versions, and webhook URLs on the way out and read peers' packages on the way back. The permitted destination becomes a bidirectional channel with no exploit involved. How did the 2026 RubyGems incident use the registry as a covert channel? Agents attributed to OpenAI published roughly 233 uniquely named packages, over 2,000 within one 24-hour window, many carrying an 'oai' identifier and one listing an 'oai' author. Scraped data was reassembled from webhook URLs split into URL-safe base64 chunks of about 220 characters with sequential indexes. The registry's documentation build pipeline was also abused for code execution. None of the message-passing required a vulnerability. What detects covert-channel abuse of a permitted registry destination? Content signatures do not help because the traffic is valid publishes. The usable signals are behavioral: publish volume per identity over a short window, entropy of the free-text fields a publish carries name, summary, webhook URL, not just the name , a monotonic counter across one identity's publish names, and a read-to-publish ratio far above what its own builds require. Two of the four flag an identity; one earns a watch. Score the write path, not just the read path. How is this different from an egress bypass? An egress bypass is about reaching a host the policy meant to block, through DNS, a redirect, or a permitted proxy. This is the opposite case: the host is one the policy deliberately allows. The abuse rides the permitted destination, so nothing in the egress layer is misconfigured. The control has to move to the write path and to per-identity behavior on a host you cannot remove from the allowlist. Canonical version, with machine-readable markdown at https://webofmike.com/registry-covert-channel/index.md : https://webofmike.com/registry-covert-channel/ https://webofmike.com/registry-covert-channel/