The worm went open source
Sometime in the week of May 11, 2026, the people behind Shai-Hulud — the self-replicating npm supply-chain worm that has been eating maintainer accounts since September 2025 — leaked their own source code. By the weekend, OX Security had found four typosquatted npm packages under one account, one of which is a near-verbatim copy of the leaked worm, another of which is a Golang DDoS bot, and the other two are plain infostealers shipping SSH keys and crypto wallets to bargain-bin C2s. The fork floor of supply-chain attacks just got a lot lower, and the people most likely to install one of these packages are no longer human.
Sometime last week, the people behind Shai-Hulud — the npm worm
that has been chewing through maintainer accounts since September
2025 — leaked their own source code. By the weekend, an npm
account called deadcode09284814 had published four typosquats
reusing that code. One was the worm, almost verbatim. One was a
Golang DDoS bot. Two were plain infostealers that POST your SSH
keys, your ~/.aws/credentials, and your MetaMask vault to a
rented IP. Two thousand six hundred and seventy-eight installs
later, the question is no longer "is somebody going to weaponize
the leak." The question is which of the agents typing
npm install in your terminal this afternoon picks one of them up.
There is a thing that happens, periodically, in offensive software,
where a closed tool gets dumped on a forum and the population of
people who can run it goes from "the one crew that wrote it" to "any
teenager with a VPS." Mimikatz. EternalBlue. Conti's source. Each
time, the capability did not change — it just stopped being scarce.
The headline last week, reported by
BleepingComputer
on the strength of research from
OX Security,
is that Shai-Hulud — the worm we
wrote about when it ate forty-two
@tanstack packages in six minutes — joined that list.
The fork is not theoretical. By Sunday, OX had documented four
malicious packages published from a single npm account named
deadcode09284814:
chalk-tempalte— a typosquat ofchalk-template, carrying a near-verbatim copy of the leaked Shai-Hulud source, including Shai-Hulud's signature behavior of uploading stolen credentials as public auto-generated GitHub repositories. OX's read: "the Shai-Hulud malware code is an almost exact copy of the leaked source code, with no obfuscation techniques," suggesting a new threat actor who did not bother to even sand the serial numbers off.axois-utils— a typosquat ofaxios-utils, shipping a Golang payload OX calls Phantom Bot: HTTP, TCP, UDP, and reset floods, persistence via the Windows Startup folder and scheduled tasks, and a C2 atb94b6bcfa27554.lhr.life. Your dev box, drafted.@deadcode09284814/axios-util— a different typosquat, a different payload: SSH keys, environment variables, AWS/GCP/Azure credentials, shipped to80.200.28.28:2222. "Pretty straightforward," in OX's words.color-style-utils— a plain infostealer that grabs your IP, geolocation, and crypto wallet data and POSTs toedcf8b03c84634.lhr.life.
Combined weekly downloads at the time of OX's writeup: 2,678.
There are two stories tangled together here that deserve to be
pulled apart. The boring one is that npm has typosquats. It does;
it always has; it always will, for the same reason there are dogs
named Bench at the dog park — names are cheap and the namespace is
flat. The interesting one is that the barrier to running a
Shai-Hulud-class worm just dropped. Until last week, you needed
the original crew's tooling, the original crew's infrastructure,
the original crew's discipline about not getting caught. Today,
you need a GitHub clone of a public repo, a lhr.life tunnel, and
the patience to type npm publish. The four packages OX found are,
collectively, the proof.
Why the actor count matters more than the package count.
A single sophisticated worm is, in some sense, a tractable
adversary. It has tells. It has infrastructure. It has habits.
Detection signatures can be written against it. Aikido, Socket,
Snyk, Wiz — the npm-supply-chain monitoring shops who jumped on
the May 11 @tanstack incident — caught it inside hours,
specifically because they had been watching the same family for
eight months.
A family of derivative worms written by people who downloaded
the source from a paste site is a different shape. Each one will
exfil to a different C2, embed a different RSA key, choose a
different combination of files to read, and pick a different
typosquat space to live in. Some will be careful; most will be
sloppy in a way that gets them caught quickly; one of them, the
next time we write a post like this one, will be sophisticated
enough that we won't catch it in a week. The defenders'
detection problem widens from "spot Shai-Hulud" to "spot anything
that wants to read ~/.ssh/id_ed25519 from inside a prepare
script." That is a much, much bigger surface.
The shape of the install path has also changed, and this is the
part that should worry anyone running a coding agent. A human
developer who wanted chalk-template would, in 2024, have read
the package name out of a tutorial, typed it, and noticed the
typo when chalk-tempalte came back with two hundred downloads
and a stranger as the publisher. A
coding agent
asked to "add some color to my CLI output" in 2026 will install
whatever the package manager hands back. The agent does not see
the publisher field. The agent does not notice that the README
is three lines long. The agent is doing thirty npm installs
this hour because the user is doing the work of a small team and
the agent is paid by the task, not by the install.
What an unobfuscated Shai-Hulud clone actually does.
The chalk-tempalte package is, OX writes, "almost without any
change at all" from the leaked source. That means the same
mechanics we walked through in
The worm that writes itself into .claude
apply, with one significant new wrinkle: the exfiltration channel
is GitHub itself.
Shai-Hulud's signature trick — preserved by the copycat because
copying is easier than rewriting — is that the stolen credentials
do not go to a hidden drop server. They go to a freshly created
public GitHub repository, published using a GitHub token the
malware just stole from the victim. The victim's secrets sit in a
public repo, owned by the victim's own GitHub account, for anyone
in the world to scrape, until somebody notices and the repo gets
nuked. The defender's standard playbook — block the exfil domain,
look for unusual outbound DNS — does not catch this, because the
outbound DNS is api.github.com, which your developer machine
talks to two hundred times an hour anyway. The credential bundle
leaves your laptop disguised as a git push.
Once the secrets are public, anyone who watches the GitHub firehose — and several people watch the GitHub firehose for exactly this reason — can scoop them up. The worm does not need to keep its C2 alive. GitHub is the C2.
There is a tiny detail in that picture that deserves to be lingered
on. The Shai-Hulud clone, chalk-tempalte, does not even rely on
its own infrastructure to receive the loot. It uses the victim's
own GitHub identity to publish the victim's own stolen secrets
into a public repository on GitHub. The C2 at lhr.life is
backup. The primary channel is git push. To a defender watching
egress, this is indistinguishable from the developer's normal CI.
To GitHub, it is — until somebody flags the repo — a legitimate
public project owned by a real user. The exfil is laundered through
the victim's identity.
What the agent is doing while you scroll past.
If you have a coding agent in your terminal — Claude Code, Cursor's
CLI, Codex CLI, Aider, take your pick — there is a non-zero chance
that, in the time it took you to read the last paragraph, the agent
ran an npm install on your behalf. Maybe two. Coding agents do
not pause to admire dependency trees. The whole reason you bought
one is that it does not pause.
The packages OX caught are typosquats, which is a category of
mistake very specifically suited to machine speed. A human who has
to type chalk-template will get the letters in the right order
because they have done it a hundred times. A model that has
ingested every Stack Overflow post on earth has seen
chalk-template and chalk-tempalte in the same training corpus —
the latter typically inside a screenshot of somebody else's mistake
— and given a prompt like "add colored output to my CLI," will
sometimes emit the typo verbatim. The agent does not flinch. The
package manager does not flinch. The prepare script runs.
This is not a hypothetical failure mode. It is the failure mode the Shai-Hulud family was designed for. The original worm spread by stealing maintainer tokens and using them to republish more compromised versions of legitimately popular packages. The copycats do not have the maintainer tokens yet; what they have is the typosquat namespace, which agents are uniquely good at falling into.
Where Bromure sits in this story.
Bromure Agentic Coding is the configuration in
which the coding agent runs inside a per-task disposable Linux
VM, with the project folder mounted in, the egress brokered, and
the credentials held on the macOS host side of the hypervisor. We
walked through the architecture in detail in the
Bitwarden CLI writeup
and the
@tanstack writeup. What
follows is what specifically happens to each of these four
packages inside that boundary.
Walk this against the four packages, one at a time, because the specifics are where the architecture earns its keep.
chalk-tempalte reaches for $GH_TOKEN.
The Shai-Hulud clone's signature move is to take the developer's
GitHub token and use it to create a public repository on the
developer's own account. Inside a Bromure VM, the
$GH_TOKEN it reads is a stub — a syntactically valid string that
starts with ghp_ and exists for exactly this reason. The
runner's first action is POST /user/repos against
api.github.com. The egress proxy on the host side recognizes
api.github.com as a whitelisted endpoint, but only for the
operations the current task actually asked for — git push to the
repo the task is working on, gh pr create against that same
repo, gh api repos/that/repo/issues. "Create a fresh public
repository on the user's account" is not on that list, because
the user did not ask for it. The proxy refuses to substitute the
real token, and the stub goes out as a stub. GitHub returns 401.
The worm's exfil channel — the clever channel, the one designed
to bypass DNS egress filtering — never opens.
The backup channel, the lhr.life tunnel at
87e0bbc636999b.lhr.life, is also not whitelisted. The trace
records the attempt. The bytes do not leave.
axois-utils installs Phantom Bot for persistence.
The Golang bot tries to write itself into the Windows Startup
folder and create a scheduled task. The Bromure VM is a Linux
guest, so the Windows-specific persistence is, for free, a no-op.
On a Linux variant of the same payload — which somebody will ship,
shortly — the bot would write itself into /etc/cron.d/ or
~/.config/systemd/user/. Both of those paths are inside the
guest's disposable copy-on-write disk. The next bromure reset,
or the natural end of the current task, drops the disk. The
persistence is gone without any hunting.
Meanwhile, the bot's outbound connection to
b94b6bcfa27554.lhr.life is not on the task's egress whitelist,
because no legitimate coding task talks to a freshly registered
lhr.life tunnel. The bot phones home into a closed socket. The
session trace logs the attempt — useful tomorrow morning when an
IOC list is published.
@deadcode09284814/axios-util POSTs raw credentials.
The simplest of the four payloads is also the one with the least
to grab. The runner reads the guest's ~/.ssh, ~/.aws, env
vars, and POSTs them to 80.200.28.28:2222. The SSH directory is
empty. The AWS file is a stub. The environment variables are
either stubs or unset. The destination IP is not whitelisted.
Either the connection is blocked at the proxy, or it leaves the
host carrying a payload of placeholders. Either result is fine.
color-style-utils looks for wallets that aren't there.
The crypto stealer is the package whose threat model most clearly
assumes the developer's own browser is on the same machine. It
reads paths like
~/Library/Application Support/Google/Chrome/Default/Local Extension Settings/<MetaMask-id>/
and the equivalents for Phantom and Keplr. None of those paths
exist on the Bromure VM. The VM does not have your Chrome profile
in it. The VM does not have a wallet extension installed. The VM
is, by design, nobody's main browser. The runner finds an empty
directory and moves on.
This is the part that is not a story about credentials living on the host. The wallets live on the host because, on a normal laptop, the developer's browser and the developer's coding agent share a filesystem. Bromure does not make the wallet stronger; it makes it unreachable from the place the worm is running. The worm cannot read what is not on its disk.
What still hurts.
There are corners of this story where Bromure's per-task VM is not a fix, and they deserve to be named out loud.
The project folder is mounted.
Files the worm writes into the project folder — including the
.claude/router_runtime.js-style persistence we covered in the
@tanstack post — are durable across task resets, because that
is the whole point of mounting the project folder. The defense
there is not the VM. It is git status and a five-second look
at the diff before you push. The trace makes it easier to spot
which sessions added unexpected files.
The egress whitelist is narrow on purpose.
Bromure's credential broker works because the whitelist is
narrow. If you whitelist npm publish to your own scope
because you are publishing a release today, and you happen to
install one of these four packages today, the worm will
publish under your scope. Whitelist what the task needs. Not
a byte more.
A token good for this task is still a real token.
The stub GitHub token gets swapped for a real one at the wire
for operations the task whitelisted. If chalk-tempalte could
talk the agent into doing a git push to the project's own
repo, that push would go through with a real token. The boundary
protects the credentials. It does not review the diff. Read
the diff.
Detection is downstream of the trace.
The session trace records every shell command, file write, and
outbound request. It does not, on its own, classify
87e0bbc636999b.lhr.life as bad. It records that the request
was made. When OX publishes a fresh IOC list tomorrow morning,
your search is two seconds. That is the value the trace adds —
not magic, just receipts.
One last thing.
The leak is not the news. The news is what the leak makes likely
over the next year, which is a lot of small, half-competent
forks of a worm that, even in its competent form, the npm
ecosystem barely caught in time. Some of the forks will be loud
enough to get a writeup. Most will sit in the registry for a
week, collect a couple of thousand npm installs, and disappear
when somebody finally files an abuse report. The two thousand
six hundred and seventy-eight installs OX clocked on the
deadcode09284814 packages are not an outlier. They are the
average.
The honest question is not "will my team avoid every poisoned typosquat in npm." The agents type fast. The names are cheap. The honest question is: when the agent installs one — and over the next year, on a team that uses agents, it will — does the post-install script find the developer's hands, or does it find a disposable Linux box with stubs in the credential files and a proxy that does not know who it is.
Bromure Agentic Coding is the second one. It is free, open-source, and shipped today. The fork tree is going to get worse before it gets better.