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.
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.
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.
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.