testmetest: An npm Worm Is Being Tested in Public, With a Wiper Attached

Written by the Rafter Team

As of October 22, 2026, 19:30 UTC.
Someone is testing a self-spreading npm worm on the public registry, and the test copies work. No one outside the operator's own accounts is known to be hit. What matters today is the cleanup order: on an infected machine, revoking the stolen GitHub token first triggers a wiper.
What happened
Between October 19 and 22, throwaway npm accounts published packages whose names end in testmetest.
SafeDep's write-up, published October 22, counts 15 of them and calls the activity "an operator testing a Miasma-family npm worm in public."
SafeDep says the code overlaps with the June Miasma "Phantom Gyp" wave but has not confirmed its lineage.
The timeline, from the npm registry's own publish records (UTC):
- October 19, 10:18.
sdk-utils-testmetestappears from the accounttestnewtestme. - October 20, 22:51.
kvarzo8f3atestmetestfollows from the same account. - October 21, 10:32 to 13:35. A burst of packages, including
data-pipeline-testmetest, whose version 2.0.1 at 11:13 is the full stealer according to its OpenSSF malicious-package record. - October 21, about 15:07. The registry shows that burst unpublished within a few seconds. It does not say who removed them.
- October 22, 10:01 onward. The account
alexjoipamibapublishesevent-bus-lite-testmetestandretry-utils-testmetest. SafeDep flagsevent-bus-lite-testmetest@0.0.1as armed. - October 22, 18:32. The registry shows the operator still publishing after SafeDep's post:
event-bus-lite-testmetestis at 0.0.6 andretry-utils-testmetestat 1.0.3. Both were live when this was written.
The sources differ on the edges.
SafeDep names two publishing accounts; the OpenSSF records name a third, testnewtestme2, and list log-sink-testmetest and test-brotli2-testmetest, which are missing from SafeDep's 15.
Treat any package ending in testmetest as malicious.
What the payload does
The loader runs on npm install with no preinstall or postinstall script in sight.
It ships a binding.gyp file, the build recipe for a native addon.
npm's docs say that when a package has one and no install script of its own, npm runs node-gyp rebuild.
The recipe's source list holds a shell command, so the "build" runs the attacker's index.js, which decrypts a payload and runs it with the Bun runtime.
A review that only reads the scripts field sees nothing.
According to SafeDep, the payload then:
- Steals credentials. SSH keys, npm, PyPI and Git tokens,
.envfiles, browser logins, AWS, GCP and Azure keys, Kubernetes secrets, AI tool secrets, and GitHub Actions secrets read from runner memory. - Spreads. It publishes infected patch versions of packages the victim can publish to npm, PyPI, RubyGems and JFrog, and tries SSH hosts listed in
known_hosts. - Plants files for coding agents. It commits a fake CodeQL workflow plus
.claude/settings.json,.cursor/mcp.json,CLAUDE.md,AGENTS.mdand.vscode/tasks.jsonto the victim's GitHub and GitLab repositories. Anyone who opens those repos in an agent or editor that honors those files can run the worm next. - Holds the token hostage. A service named
net-health-checkerchecks the stolen GitHub token every 45 seconds for 48 hours. On a 401 or 403 it runsrm -rf ~/; rm -rf ~/Documents.
In GitHub Actions it also disables StepSecurity's Harden-Runner.
The hostage trick is not new this month.
StepSecurity documented the same move in the compromised tensorlake package on October 8.
Two waves in three weeks make it a pattern.
It also changes incident response: the reflex to revoke first is now the step that destroys the evidence and the machine.
Who is exposed
Today, very few people. These are new packages with odd names. SafeDep says that as of October 22 it has "found no compromised third-party packages or victims."
The risk is what the test is for.
Earlier Miasma waves did not use throwaway names; they republished real packages from stolen maintainer tokens, such as ai-sdk-ollama in June.
If this variant reaches one maintainer's machine, its next copy ships inside a package you already use.
Coding agents raise the stakes in two ways.
An agent that runs npm install unattended runs node-gyp with it, and nobody watches the output.
And the repo files this worm plants are aimed at agents: a poisoned .claude/settings.json or AGENTS.md turns the next clone into the next install.
We covered that pattern in TrapDoor and in The Coding Agent Is the Supply Chain Now.
Elsewhere this week, SafeDep found forged commits adding a preinstall dropper to the hugo-paper and mevn-cli GitHub repos; the published mevn-cli package is clean.
And LiteLLM 1.107.0 was compromised on PyPI on October 21, which is a separate story.
What to do now
- Check whether you installed one. Search lockfiles and installed trees:
grep -rn testmetest package-lock.json pnpm-lock.yaml yarn.lock bun.lockin each repo, andnpm ls --all | grep testmetest. Check CI caches and developer machines, not only the repo. - If you find one, isolate the machine before you touch any token. Take it off the network, then stop and remove the persistence: on Linux,
systemctl --user list-unitsand look fornet-health-checkerorpkg-runtime; on macOS, check~/Library/LaunchAgents. Only then revoke the GitHub token. Reversed, this order runs the wiper. - Rotate everything the machine could read. GitHub and GitLab tokens, npm, PyPI and RubyGems tokens, cloud keys, Kubernetes credentials, SSH keys, AI provider keys and anything in a
.envfile. SafeDep advises reinstalling the operating system. - Audit what the token could reach. Look for repositories you did not create (SafeDep and OpenSSF both mention
project-utils), patch releases you did not publish, and recent commits that add.claude/,.cursor/,CLAUDE.md,AGENTS.md,.vscode/tasks.jsonor a CodeQL workflow you did not write. - Install from the lockfile in CI. Use
npm ci, which installs only what the lockfile pins and fails ifpackage.jsondisagrees with it, so a new transitive version cannot slip in during a build. - Turn off install-time execution where you can. Set
ignore-scripts=truein.npmrcfor CI and agent sandboxes. In our test on npm 10.8.2,--ignore-scriptsalso stopped abinding.gypcommand from running, while a default install ran it. Some native packages will need an explicit rebuild step. - Add a release-age delay. npm's
min-release-agesetting installs only versions older than a set number of days; pnpm hasminimumReleaseAge. Every package in this campaign was less than four days old. - Give agents their own credentials. An agent that installs packages should not run in a shell holding your GitHub token, cloud keys and SSH agent.
Where Rafter fits
Rafter will not tell you whether you installed one of these packages, and its command gating rates npm install as low risk, so it will not stop this install either.
What the open-source CLI can do is shrink what a stealer finds.
rafter secrets . scans a directory for secrets locally, with no account or API key, so plaintext keys sitting in .env files and configs show up before a worm reads them.
For the wider pattern, see install-time execution as a bug class and our Shai-Hulud retrospective.
The rehearsal is the warning
The operator is debugging in the open: test names, version numbers that go backwards, new builds hours after a public write-up. That is cheap for them and useful for you. When a copy turns up in a package with a real name, the first decision your team makes, which token to revoke and when, decides whether you keep the laptop. Write that order into your runbook today.