A credential-stealing npm worm known as testmetest has moved beyond its own test packages. JFrog has also found 42 real GitHub repositories, hijacked by the same campaign, rigged to run a hidden payload as soon as someone opens the project in an AI coding assistant or IDE. If your developers installed one of the packages below or cloned an affected repository, treat the machine as compromised, and remove the worm’s persistence before you revoke any token: revoking first triggers a wiper that deletes the home folder.
What happened
SafeDep (opens in a new tab) first described the npm side on 22 October. Between 19 and 25 October, an operator using the npm accounts alexjoipamiba, testnewtestme and iobandamwer published throwaway packages, most ending in -testmetest, whose malicious versions steal credentials and spread themselves. Releases from 19 to 22 October run at install time without a declared install script: a binding.gyp build file tells npm to run the loader while “building” the package. The @iobandamwer builds of 24 and 25 October instead use a postinstall script to load a compiled native addon (addon.node, addon_win.node), which decrypts the payload from a hidden .napi_cache file, so there is no readable JavaScript to review. At that point SafeDep had found no victims outside the operator’s own test accounts.
On 26 October JFrog Security Research (opens in a new tab) reported that the same campaign had already armed 42 repositories belonging to 11 legitimate GitHub owners, including established developers, students and an organisation, from late September. Each carries a .github/init.js of 12.9 to 13.7 MB, plus hook files that run it: for example a Claude Code SessionStart hook running node .github/init.js || bun .github/init.js, and equivalents for Gemini CLI, Cursor and VS Code. JFrog decoded these files to a hidden npm-publishing payload, related to the worm’s tooling, that harvests tokens and publishes packages. JFrog says the files were too large for GitHub code search and the contents API to show, which is how they went unnoticed. Many commits in those repositories copied author dates and messages from earlier, legitimate history, so commit dates cannot be trusted.
Once running, the npm worm steals SSH keys, npm, PyPI, Git, cloud, Vault and Kubernetes credentials, browser data and AI-assistant tokens. It sends them to a new repository in the victim’s own GitHub account (falling back to a web endpoint), then uses stolen registry tokens to publish infected versions of the victim’s own packages. SafeDep reports that it installs two persistent services. One, net-health-checker, checks the stolen GitHub token every 45 seconds for 48 hours. If the token stops working, it runs rm -rf ~/.
SafeDep describes it as a Miasma-family variant while noting that it has not confirmed the lineage. JFrog calls it a Shai-Hulud-descended payload and says its evidence does not establish that the original Miasma operators are responsible. Neither firm names an actor. JFrog also links the same hijacked accounts to an earlier campaign that used GitHub Actions runners to scan the internet for vulnerable cPanel servers, first reported by Socket in July.
Who’s affected
- Anyone who installed a malicious version of these 14 packages, directly or as a dependency:
data-pipeline-testmetest,event-bus-lite-testmetest,event-emitter-testmetest,log-rotate-testmetest,log-sink-testmetest,net-codec-testmetest,npm-publish-testmetest,pixel-map-testmetest,retry-utils-testmetest,test-brotli-testmetest,test-brotli2-testmetest,path-stream-utils-testmetest,@iobandamwer/cache-helperand@iobandamwer/native-helper. That is JFrog’s list of packages with malicious releases, and its classification table gives the exact versions. Counts differ between firms: JFrog lists 25 package names linked to the campaign, 14 of them with malicious releases; SafeDep counts 18 packages from the operator; Corgea counts 26 across the wider cluster, including staging and decoy packages. The difference is mainly whether stub and decoy packages are counted. When we checked the npm registry on 27 October, every one of them was either unpublished or replaced with an npm security holding package, so new installs are no longer at risk. These names have almost no legitimate users, so the realistic exposure is curious developers and CI jobs that pulled them in. - Anyone who cloned and opened an armed repository in Claude Code, Gemini CLI, Cursor or VS Code (with automatic tasks allowed), or ran
npm install,composer installormakein one. JFrog has not published the list of 42 repositories, so check your own clones rather than relying on a list. JFrog decoded the files in these repositories to a token-stealing npm publisher rather than the full worm, but treat any machine that ran one as compromised: rotate what it could reach, and check for the worm’s persistence first in case it was also installed. - Anyone whose repositories or packages an affected machine could write to. The worm adds itself to the victim’s repositories and publishes new patch versions of their packages, so your own users may be next.
What to check
Check from a terminal. Do not open a suspect repository in an AI assistant or IDE to look, as that is what starts it.
- Repositories. Look for
.github/init.jsor.github/init.ps1. A.github/init.jsover 12 MB is a strong sign. Then look at.claude/settings.json,.gemini/settings.json,.cursor/mcp.json,.cursor/rules/*.mdc,.vscode/tasks.json,CLAUDE.md,AGENTS.md,package.json,composer.jsonandMakefilefor anything that runs.github/init.js. On GitHub, list the repository’s Git tree rather than using code search:gh api "repos/OWNER/REPO/git/trees/HEAD?recursive=1"shows each file’s real size. Check every branch, not just the default one. - Git history. Branches named
chore/ci-<hex>ordeps/upd-<digits>, commits endingskip-checks:truethat you did not make, and new repositories in your account described asproject-utilsor with commits startingAutoSyncComplete:. - Packages. Lockfiles and
node_modulescontaining the names above, and any package with an unexpectedaddon.node,addon_win.nodeor.napi_cachefile. - Machines (Linux and macOS).
~/.local/bin/net-health-checker.sh,~/.config/net-health-checker/,~/.local/share/runtimes/sync.py,/var/tmp/.fontconfig_state, the user servicesnet-health-checkerandpkg-runtime(systemctl --user list-units), and the Launch Agentscom.user.net-health-checkerandcom.user.pkg-runtime. Hidden temp files named only with digits, such as/tmp/.48213, or/tmp/.hcare also worth a look. - Machines (Windows). Hidden files named
.nc<digits>.jsin%TEMP%. JFrog found no code that deletes them, so they may still be there. - Logs.
nodeorbunrunning.github/init.jsor one of those temp files, and connections to the addresses in the IOC list below.
The Hunt Lab (opens in a new tab) has Sigma rules for the process and file checks, and a free, read-only Python script that runs the repository, package and machine checks above on Linux, macOS and Windows. We tested both on synthetic data and the script on Linux only; the Hunt Lab page says exactly what was and was not tested.
What to do now
If you find signs of infection, the order matters.
- Disconnect the machine from the network.
- Remove the persistence before revoking anything. On Linux, SafeDep’s steps are
systemctl --user disable --now net-health-checker pkg-runtime, then delete~/.local/bin/net-health-checker.sh,~/.config/net-health-checker,~/.config/systemd/user/net-health-checker.service,~/.config/systemd/user/pkg-runtime.service,~/.local/share/runtimesand/var/tmp/.fontconfig_state. On macOS, runlaunchctl bootout gui/$(id -u)for each of the two Launch Agents in~/Library/LaunchAgents, then delete the.plistfiles. If you revoke the GitHub token whilenet-health-checkeris still running, it deletes your home folder. - Then revoke and rotate every credential the machine or CI job could reach: GitHub and GitLab tokens, npm, PyPI and RubyGems tokens, cloud keys, SSH keys, Vault and Kubernetes credentials, and AI-provider keys.
- Audit everything those identities could write to. Remove the hook files and
.github/init.js, delete unexpectedchore/ci-*anddeps/upd-*branches and workflows, delete anyproject-utilsrepositories the worm created, and unpublish or deprecate package versions you did not release. Save commit, workflow-run and artefact records first; you will need them. - Rebuild the machine from trusted media. SafeDep advises rebuilding any machine that ran the full worm, and JFrog’s guidance treats the stolen tokens as compromised.
For the future, this is another reason to review hook and task files in any repository before trusting it in an AI assistant or IDE, and to run installs with --ignore-scripts where you can. That flag also stops the binding.gyp trick used in the earlier releases, because npm runs it as the package’s default install script, but it does nothing about the repository hooks described above. Treat unexpected binding.gyp files and native addons in small utility packages as a warning sign. Our Node2Shell Alert covers another way attackers are getting code to run under Node.js this month, and our LiteLLM Alert another poisoned package that called for rotating every secret.
Indicators of compromise
Defanged. Published by JFrog and SafeDep (the scanner server first by Socket); we have not observed them ourselves. Payload hashes change with every build, so do not rely on them.
- npm accounts:
alexjoipamiba,testnewtestme,iobandamwer - GitHub operator account:
CementBeetleSteeple(do not clone or open its repositories) - Exfiltration endpoint (SafeDep and JFrog):
hxxps[:]//aiarmor[.]website/v1/api - Scanner command server (first reported by Socket; linked by JFrog):
hxxp[:]//43[.]228[.]157[.]68:80, with downloads from/api/dl/<arch> - Loader download:
hxxps[:]//raw[.]githubusercontent[.]com/CementBeetleSteeple/node-cache-utils/main/loader.js - Files:
.github/init.js,.github/init.ps1,.claude/index.js,addon.node,addon_win.node,.napi_cache,/tmp/.<pid>,/tmp/.hc,/tmp/.svc,%TEMP%\.nc<pid>.js - Persistence: services
net-health-checkerandpkg-runtime;~/.local/bin/net-health-checker.sh,~/.config/net-health-checker/,~/.local/share/runtimes/sync.py,/var/tmp/.fontconfig_state - Git markers: branches
chore/ci-<hex>anddeps/upd-<digits>; commit textAutoSyncComplete:,msync-3f7a,shellpanel execution smokeandskip-checks:true; repositories described asproject-utils; filesdata/snap-<timestamp>-<n>.json - SHA-256 (SafeDep, on-disk files):
837904152e824645cb7d2d58a4a5e0aaa22d5e82a5d6eafe08b9c2b8b1bf4de6(binding.gyp),0b0e02b03ea3a2f8e8657143706e327a0b3608acbc1c43ec02ebc072dbe76944(index.js),c7ef186aa59b67d9b198a14f6dc8aaee6dd86510ac7c37d28f65d310cd846411(sync.py),7625f77a61433b3a8e9b41952b7dca102d1d08eab30266fd6c6aa0b0c2ea15b1(addon.node),1685824218a45e9308e3ced9f4a1cee3fe51e516dab2bd058fc1f9de31f559c9(addon_win.node),b3f74e4b03b58eeb31ff9ecc663a118ca37695f078eb4d3cae311ddc56a55d28and67967033b1dd1e5b845c0959291df533d1102d0191885e7cb9b18d46f7744bbd(.napi_cache). JFrog lists further hashes for decoded stages.
Indicators are provided as-is for defensive use and age quickly. Check the date and source before acting on them. Terms.
ATT&CK mapping
- T1195.002 Compromise Software Supply Chain: malicious npm versions and armed repositories
- T1059.007 JavaScript:
nodeorbunrunning the loader - T1543.002 Systemd Service and T1543.001 Launch Agent:
net-health-checkerandpkg-runtime - T1027 Obfuscated Files or Information: the encrypted
.napi_cacheand oversized wrappers - T1497.001 System Checks: refuses to run in CI, containers and debuggers (JFrog)
- T1552.001 Credentials In Files: SSH keys, registry tokens and cloud credentials
- T1567.001 Exfiltration to Code Repository: stolen data committed to the victim’s GitHub
- T1485 Data Destruction: the home-folder wiper
Sources
- JFrog Security Research: Beyond testmetest: Native Addon Camouflage and 42 Armed GitHub Repositories Reveal a Hidden npm Publisher (opens in a new tab) (published 26 October 2026), checked 27 October 2026
- SafeDep: testmetest npm worm (opens in a new tab) (published 22 October 2026, updated 26 October 2026), checked 27 October 2026
- npm registry (opens in a new tab) entries for each package named above (unpublished or security holding package), checked 27 October 2026
- GitHub REST API: Git trees (opens in a new tab), checked 27 October 2026
- MITRE ATT&CK (opens in a new tab), checked 27 October 2026
Follow Cyber-Threat.net RSSFeedly (opens in a new tab)X (opens in a new tab)Bluesky (opens in a new tab)LinkedIn (opens in a new tab)



