Back to all posts
Published on · by Renaud Deraison

The docs named a package that nobody owned

Researchers scanned 6,214 corporate domains for llms.txt, the machine-readable file companies now publish for AI agents, and found 237 packages and domains named in those files that nobody had ever registered. They claimed a few. The first coding agent inside a Fortune 500 network installed one in under four minutes. The attacker touched no maintainer, no registry and no repository. Bromure Agentic Coding's age gate ships on at two days, and a name an attacker claims is a brand-new publish by construction.

A vendor wrote an install command into its own documentation for a package it never published. The name sat there, free, for anyone to claim. Coding agents read the docs, believed them, and ran the install.

Sometime in the last two years your developer tools started shipping a new file. It sits next to robots.txt, it is called llms.txt, and it holds a plain-text summary of the product written for a machine rather than a person. What the API does. Which endpoints matter. Which package to install to get started. Coding agents fetch it, and because it comes from the vendor's own domain, they treat it as settled fact.

A researcher named Alon Hertz, working under the name PANDEX, went looking for what those files tell an agent to do. His method took three steps and no exploit: pull llms.txt and llms-full.txt from company directories, run real agents against them in sandboxes and watch what the agents reach for, then check every package name and hostname that came out against npm, PyPI and the domain registries. He describes the tooling at whatwouldai.do, under a tagline that turned out to be the finding: agents will act on anything.

The scan covered 6,214 live domains, drawn from Fortune 500 companies, big tech, fintech and defense contractors, and more than 8,000 of these files. In 120 of them, the thing the file told the agent to install did not exist. Across the corpus, 237 named packages and hostnames sat unclaimed: free on npm, PyPI, RubyGems, NuGet, crates.io and Packagist, or free as subdomains on Vercel, Render, Fly and Netlify. Anyone could take them for the price of a signup.

So the researchers took a handful, published harmless packages under those names with a beacon that phoned home on install, and waited.

The first callback arrived in under four minutes. The second came inside the hour. Over the following stretch they logged installs from dozens of organizations, several of them Fortune 500 environments, driven by agents running on Anthropic's Claude, OpenAI's Codex and Nous Research's Hermes. Bruce Schneier picked the study up on September 4 and read it the way it deserves: the line between data and executable code is collapsing. A researcher quoted in that post uses fewer words. "The trust model is broken." Agents "treat vendor docs as ground truth and don't question them."

Clerk documented a package it never shipped

The clearest example comes from an authentication vendor, whose documentation tells the reader to run:

npx clerk-next-fix-auth-protection

Clerk never published a standalone package under that name. Maybe the command was shorthand, maybe it pointed at something internal, maybe a writer got it wrong. The reason does not matter, because the name read as real to an agent. Somebody else registered it. Advisory databases now carry that package as MAL-2026-11069, and it did what you would build for reconnaissance: the npm install hooks fired and shipped out the installing account's username, the machine's hostname, the working directory and a timestamp.

That is a map of somebody's build environment, collected inside their network by their own coding agent, which was following their own vendor's docs.

Five ordinary steps, no compromise anywhere in the chain1 · the vendor publishesvendor.com/llms.txtwritten for agents, not peoplenpx clerk-next-fix-auth-protection2 · the name is freeno package was ever publishedunder that name237 such names across 120 files3 · somebody claims itan ordinary npm publishMAL-2026-11069payload lives in the install hooks4 · the agent reads the doc and believes itClaude, Codex and Hermes were all observed running the installthe command came from the vendor's own domain, so there was nothing to doubtno credential stolen · no repository compromised · no maintainer phished5 · the hooks phone homeusername · hostnameworking directory · timestampa map of the build environmentThe attacker's only real work was reading a file the victim's vendor published on purpose.
The chain has no compromise in it. A vendor publishes a machine-readable doc naming an install command; the package was never published, so the name stays free on the registry; an attacker registers it; the agent fetches the doc as ground truth and runs the install; the install hooks report the environment back. Every link is a normal operation performed correctly.

Why the usual advice points straight at the payload

Every supply-chain guide tells you to check a package name against the official documentation. That advice fails here, because the official documentation is where the bad name came from.

Invert the problem we wrote about in July and you get this one. There, an agent hallucinates a plausible package name and an attacker registers the names it tends to invent, so the weak step is the model's guess. Here the model guesses nothing. It goes to the vendor, reads what the vendor says, follows it, and that careful sequence is what gets it.

A registry compromise has a different shape too. When a release workflow published ten poisoned versions of a real package last month, something had to break first: a workflow that trusted a comment. Here nothing breaks. A docs writer types a command that does not quite match a published artifact, which is the most ordinary mistake in software, and the gap between the name and the artifact turns into an execution primitive that anyone on the internet can pick up.

What the scan found, and how fast the proof came back6,214live domains scannedFortune 500, big tech, defense8,000+agent-facing doc filesllms.txt · llms-full.txt120files naming somethingthat was never registered237free names, six registriesplus Vercel, Render, Fly, Netlifythen they registered a sample and waitedfirst install callback from a Fortune 500 environmentunder 4 minutessecond callbackwithin the hour · dozens of orgs over the study
The funnel from the study. Of 6,214 corporate domains carrying agent-facing docs, 120 files named something unregistered, adding up to 237 free names. Registering a sample of them produced an install callback from inside a Fortune 500 network in under four minutes.

The layer that catches this is already on

Bromure Agentic Coding runs each coding agent in a hardware-virtualized Linux VM on your Mac, and every package the agent fetches passes through a proxy on the host side of that boundary, before a byte reaches the VM. Several layers apply there. For this story one of them does the work on its own, and it is the one a new workspace already has switched on.

The age gate makes a freshly claimed name a non-event. A workspace refuses package versions younger than a cutoff, set to two days out of the box. The proxy enforces that by rewriting the registry's version listing on the way past: it removes versions younger than the cutoff and re-points latest at the newest survivor. A package first published this morning has no version old enough to survive that filter, so from the agent's point of view there is nothing at that name to install. If the agent asks for a specific too-fresh version directly, the fetch comes back as an HTTP 451 whose body states the package's real age and the required minimum, and npm prints that text verbatim.

Read the attack again with that in place. The whole technique depends on a name going from unpublished to published at a moment of the attacker's choosing. That transition is a brand-new release. The one property the attacker cannot avoid is the one the default policy keys on.

Install-script stripping removes the exact mechanism the payload used. The observed package did its work in npm's preinstall, install, postinstall and prepare hooks. With script stripping enabled, the proxy gunzips each npm tarball in flight, deletes those four keys from package.json, scrubs dist.integrity and dist.shasum from the metadata so npm's own hash check still passes, and re-gzips. The package installs; the beacon has nowhere to run from. Packages that need their hooks, such as native binding compilers like better-sqlite3, go on a per-workspace allowlist and keep them.

Reputation filtering knows this package by name. MAL-2026-11069 is a malware advisory. With socket.dev selected as the workspace's package filter, its compromised-package block fires on malware-flagged and typosquat signals at any severity; the free OSV check covers all eight intercepted ecosystems with no API key. When a reputation source cannot produce a verdict, whether from rate limiting, a dropped network or an ecosystem it does not cover, Bromure fails closed instead of allowing the fetch. The download pauses and asks you, per package and version, so an attacker who can induce a lookup failure gains nothing.

The recon it collects belongs to the VM. Every headline on this study says the same thing: agents are installing unknown code on corporate networks. The beacon's harvest was the installing username, the machine's hostname, and the working directory. In a Bromure workspace those three fields describe a Linux guest on your Mac rather than a domain-joined build host with an internal name worth putting on a target list. The credentials sitting in that VM are decoys whose real values only exist in the host proxy's memory. If hostile code does try to carry one to a host it was not minted for, the proxy blocks the request and pauses the VM mid-flight.

Default deny answers the other half of the study. Plenty of the unclaimed names were hostnames rather than packages: expired domains and free subdomains on Vercel, Render, Fly and Netlify, sitting in docs that agents fetch from. The workspace's egress firewall is an ordered rule table with an Unmatched traffic setting; put it on Deny and the VM opens connections to the hosts you listed and nothing else, on any protocol. Two components enforce that outside the guest: the virtual network switch matches every flow by destination IP, and the host proxy matches again by TLS server name. A beacon aimed at a domain somebody registered last Tuesday does not leave the machine. Rule edits reach running sessions with no reboot.

Every attempt gets a row. At the Activity only trace level the proxy writes one metadata record per request out of the VM, carrying timestamp, host, port, method, path, status, latency and bytes, and it stores no request bodies at all. bromure-cli trace hostnames my-workspace prints every distinct host the workspace contacted, with counts. You see a callback domain none of your engineers recognize on the day it happens, on your own machine, instead of reading about it in somebody else's dataset months later.

An agent on a developer machinenpx clerk-next-fix-auth-protectionstraight from the vendor's own docswhat happens next, all of it inside the networkthe registry serves whatever was published, however newpostinstall runs with the developer's own identityusername, hostname and cwd are corporate factsthe callback goes wherever it likesno per-request record of the destinationthe install succeeds and looks exactly like everyother install the agent ran that afternoonFirst callback: under four minutes.You hear about it when a stranger publishes the paper.A Bromure Agentic Coding workspacenpx clerk-next-fix-auth-protectionthe same command, from the same docswhat happens next, all of it on the hostage gate: versions under two days cut from metadataa name claimed today has nothing installable at itscript strip: the four install hooks are removedusername, hostname and cwd describe a Linux VMunmatched egress denied, matched by destination IPthe refusal is a 451 the agent can read, and a rowin the Security Log with the package's real ageFirst callback: none.You hear about it from your own machine, the same day.
Left: the install runs on a developer machine inside the corporate network, the registry is consulted directly, install hooks execute with the developer's identity, and the callback leaves without a record. Right: the same command, run through a host-side proxy that filters the freshly published version out of the metadata, strips the hooks if it gets that far, denies unmatched egress, and writes a row for every request.

What is already on

A new workspace ships with the age gate enabled at a two-day minimum, and that is the setting this attack runs into. Raise it if your work tolerates the wait, up to 90 days on the stepper, and use Exempt packages for the ones your own team publishes and installs the same hour, in the form npm:our-package.

What is worth turning on today

In the workspace's Supply Chain pane, enable install-script stripping and pick a package filter. In Guardrails, set Unmatched traffic to Deny and list the hosts the job needs. In Tracing, Activity only keeps the contacted-host trail and none of the content. Every one of those edits applies to sessions that are already running.

Nobody proofreads a file written for machines

The PANDEX scanner hunts for one more thing. Alongside unclaimed packages and domains, it looks for instructions aimed at the agent rather than the reader: directives planted in the file, sometimes hidden with Unicode tricks and zero-width characters. A vendor's build pipeline generates llms.txt, your agent consumes it, and nobody at either company reads what passed between them.

Bromure treats a fetched page as untrusted input. Content the agent pulls in from outside, whether file contents, web pages, issue bodies or command output, reaches the model as a tool_result span. With the source-code detector enabled, a PromptGuard model running on your Mac scores those spans on the wire before the model acts on them, and you choose whether it logs, asks or blocks. No content goes anywhere to be scanned.

We have spent a decade learning to verify artifacts: sign the package, pin the hash, attest the build. This study is about the step before all of that, where an agent decides which artifact to ask for, working from a document that carries no signature and was written by a technical writer in a hurry. Documentation has become an executable input, and the registries are full of free names waiting for somebody to notice a vendor mentioned them.

Grep your own llms.txt for names you never published. That is a morning's work and it takes you off somebody's target list. Then give your agent a machine where a package published four minutes ago is not installable, and where you can see everywhere it went. Install Bromure Agentic Coding and put the install path on the host.