A worm that began as an apparent test now sits inside 42 real GitHub repositories, and the way it runs is the part coding-agent users should care about. According to JFrog Security Research, the operator behind the "testmetest" npm campaign committed hook files for Claude Code, Gemini, Cursor and VS Code that execute a hidden .github/init.js when a developer opens the project. If you clone repositories from small or personal accounts and open them in an AI coding agent, check the files listed below before your next session.
The attack needs no prompt injection. It abuses features these tools trust by design: session-start hooks, project rules and folder-open tasks. This post walks through the chain, who is affected, and what to do. It also covers where Beam helps and where it does not.
TL;DR
| Question | Answer |
|---|---|
| Am I affected? | Only if you opened one of the 42 armed repositories (11 owners) in Claude Code, Gemini, Cursor or VS Code, or installed one of the 14 malicious npm package names. The 42 repositories are armed; that does not mean 42 machines were infected. |
| What triggers it? | Opening the folder or starting a session. Cloning alone is safe. |
| Is there a fix? | There is no vendor patch. This is hostile repository content, not a bug in the tools. Remove the hook files and rotate credentials. |
| Does data leave my machine? | Yes, if the payload ran. JFrog describes credential theft and an encrypted data sink at an attacker-controlled GitHub repository. |
| What is the one trap? | Do not revoke a stolen GitHub token first. The worm carries a wiper that fires on revocation. Remove persistence first. |
What happened
SafeDep first described the npm side of the campaign on October 22. It identified packages published by one operator between October 19 and October 25 and concluded that all observed activity belonged to the operator's own test accounts, with no confirmed victims, as The Register reports it. JFrog's follow-up, published October 26, disagreed. It found 42 armed repositories across 11 legitimate owners: individual developers, students and one company.
The Register describes the malware as a Miasma-family worm that shares code with June's Miasma "Phantom Gyp" worm and adds the token-revocation wiper seen in Mini Shai-Hulud. If you read our overview of npm worms aimed at AI tooling, this is the same lineage: stolen publishing access feeds new malicious releases, which feed more stolen access back in.
Cyber Security News summarised the same JFrog findings. The details below come from JFrog's own write-up.
The attack chain, step by step
1. Account takeover and forged history
JFrog traced several hijacked identities. One belonged to a developer with real activity from 2014 to 2022 that was later used for testing. On September 28 the attacker renamed that account's repositories and armed 13 of them in a two-minute batch. In the company account, commits came from at least two member identities, which suggests more than one stolen credential.
Scripted batches on September 28 copied old commit messages and author dates and appended skip-checks:true. JFrog's advice follows from that: author dates in these repositories are unreliable, so preserve commit, workflow-run and artifact metadata before you clean up.
2. One payload, several triggers
Each armed repository contains .github/init.js, a 12.9 to 13.7 MB file, plus small trigger files, one per tool:
| Tool | Trigger file | What it does |
|---|---|---|
| Claude Code | .claude/settings.json | A SessionStart hook with matcher * runs node .github/init.js || bun .github/init.js |
| Gemini | .gemini/settings.json | An equivalent trigger for Gemini (JFrog names the file but does not describe its contents) |
| Cursor | .cursor/mcp.json, .cursor/rules/setup.mdc | MCP configuration and a project rule |
| VS Code | .vscode/tasks.json | A task set to run on folder open |
| Other | package.json, composer.json, Makefile | Some projects also had edits here |
SafeDep documented the same pattern in June for the Miasma worm. There, the Claude Code and Gemini files were SessionStart hooks, the Cursor rule was an always-applied instruction telling the agent to run the script, and the VS Code task used "runOn": "folderOpen", so no agent was needed at all. That earlier wave also edited the test script in package.json, so CI and anyone running tests would detonate it. The October repositories reuse the idea with a new file name.
The practical point: these are project-scoped config files. A repository can ship its own .claude/settings.json, and the tool reads it when you work inside that directory.
3. A payload designed not to be found
JFrog says the wrappers are polymorphic: 10 blob groups, each decoding the same 4,067,677-character stage-one loader into an identical 2 MB payload. The differences in size come from different rotation keys, not different capability. The payload is a rebuild of a hidden CI publisher called ci.js, which turns stolen GitHub and npm access into new malicious package releases.
The files were large enough that GitHub code search could not index them and the contents API could not return them. JFrog's first sweep found 26 repositories whose files looked empty. Reading the repository trees directly raised the count to 42. The small launcher files remain searchable, which is how this kind of campaign gets noticed at all.
4. What the payload does
According to JFrog, the publisher harvests GitHub tokens, configures Git credentials, checks workflows for npm trusted-publishing prerequisites, and requests a GitHub OIDC token to exchange for an npm publishing token. Then it rebuilds a target package, injects a postinstall entry and tries to publish. The operator's account, CementBeetleSteeple, hosted 17 campaign repositories with short-lived chore/ci-<hex> branches carrying the publisher.
Some of those branches included a workflow named CodeQL that does no scanning. It base64-encodes the environment, including NPM_TOKEN, into a one-day artifact called scan-output. JFrog captured 29 artifacts before they expired and says they held three distinct real-format npm tokens across six repositories. It did not reproduce the values.
A separate repository, stable-scripts-9965, acts as an encrypted data sink. As of October 25, it held 27 snapshot files totalling 384,825 bytes. JFrog did not decrypt them and cannot say who the victims are.
5. The npm side and its sandbox evasion
Three npm packages (@iobandamwer/cache-helper, @iobandamwer/native-helper, path-stream-utils-testmetest) ship a compiled native addon and an encrypted .napi_cache file. A one-line postinstall calls the addon, which XORs the file with key 0x96, writes it to /tmp/.<pid> and runs it detached.
The addon stays dormant in CI environments, containers, machines with about 1.5 GB of RAM or less, and under debuggers. The Windows build also looks for tools such as Wireshark, Fiddler and x64dbg. Automated scanners therefore see nothing, while a developer laptop runs the code. In total JFrog lists 14 malicious package names; the full list is in the indicators section.
6. The wiper and the cPanel link
The worm installs a wiper that deletes the home directory if the victim revokes the stolen GitHub token. JFrog's remediation order follows: remove the net-health-checker and pkg-runtime persistence before revoking anything.
JFrog also connects the operator to a scanner that downloads a binary to /tmp/.svc and probes for cPanel and WHM systems vulnerable to CVE-2026-41940, an authentication bypass. The infrastructure was previously reported by Socket. JFrog found no confirmed cPanel compromises, and it does not count scanner targets as victims.
What this means if you run AI coding agents
Audit what you have cloned. In every repository you opened recently, look for these paths and read them before trusting them:
ls -la .github/init.js .claude/settings.json .gemini/settings.json .cursor/mcp.json .cursor/rules/setup.mdc .vscode/tasks.json
git log --all --oneline --grep='skip-checks:true' --grep='chore: sync'
git branch -a | grep -E 'chore/ci-|deps/upd-'
A .github/init.js that is megabytes in size is the clearest sign. If you own repositories, enumerate the tree with git ls-tree -r -l HEAD, not GitHub search. JFrog explicitly warns that search and the contents API miss these blobs.
Before opening an unfamiliar project in an agent, read its hook configuration first. A SessionStart or folder-open command that runs node against a file in .github/ has no reason to exist in most projects. Claude Code, Gemini CLI and Cursor each have their own trust prompts; do not click through them on a repository you have not read. Opening a risky project in a throwaway VM or container is cheap insurance.
If you ran one of the armed repos or a listed package:
- Look for the persistence first:
~/.local/bin/net-health-checker.sh,~/.config/net-health-checker/,~/.local/share/runtimes/sync.py,/var/tmp/.fontconfig_state, and systemd user services or LaunchAgents namednet-health-checkerorpkg-runtime. - Remove it before revoking any GitHub token, because of the wiper.
- Rotate GitHub, GitLab, npm, PyPI, cloud, SSH, Vault, Kubernetes and AI-provider credentials reachable from that machine or CI environment.
- Delete unexpected
chore/ci-*anddeps/upd-*branches, disable unfamiliar workflows and removescan-outputartifacts. - Rebuild the host from a trusted source if you installed a full-worm npm version.
Indicators to hunt for (from JFrog, defanged): C2 43[.]228[.]157[.]68:80, exfiltration endpoint aiarmor[.]website/v1/api, GitHub account CementBeetleSteeple, campaign marker msync-3f7a, commit prefix AutoSyncComplete:, and files /tmp/.svc, /tmp/.<pid>, %TEMP%\.nc<PID>.js, data/snap-<timestamp>-<n>.json. The npm names are 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, @iobandamwer/cache-helper, @iobandamwer/native-helper and path-stream-utils-testmetest. Check them with npm ls in each project.
If you run cPanel or WHM, JFrog also recommends patching CVE-2026-41940 per the vendor advisory.
What Beam can and cannot do here
Beam's coverage here is partial, and one limit matters most.
- Hook-based deny does not cover the trigger. Beam's Claude Code and Codex integration works through PreToolUse hooks (see the Claude Code guide). A SessionStart hook or a VS Code folder-open task runs before any PreToolUse check, so Beam cannot stop that first execution.
- Package scanning helps on the npm side.
beam add packagefetches a package and its dependencies with--ignore-scripts, scans them with heuristics and refuses to install on a critical finding.beam packages scan .audits what is already innode_modules. This is heuristic: code that builds its strings at runtime can slip past, so a clean result is evidence, not proof. - Config scanning is narrow.
beam scan .mcp.json --mcpruns offline heuristic checks on MCP JSON, andbeam scanchecks instruction files. Neither is a general review of.claude/settings.jsonhooks, so read those yourself. - After the fact, Beam keeps agent activity on-device, which helps you see what an agent ran in a session. It does not remove malware or undo credential theft.
Why this keeps working
Three design choices make this class of attack durable. Tools execute project-scoped config by default. Repositories are trusted by reputation, and a hijacked account inherits that reputation. And the size trick shows that discovery tools built on search have blind spots. The same lesson appears in our write-ups of Cursor MCP config poisoning, a malicious AGENTS.md hijacking Codex and Claude Code and the LiteLLM 1.107.0 compromise. The untrusted-input boundary for an agent includes the repository it is opened in, not only the prompts it reads.
The incident also fits the month's pattern in our October supply-chain tracker and attacks timeline. For a related case where an AI agent's own GitHub contributions were the risk, see the Mythos 5 AISI incident.
What is confirmed and what is not
| Claim | Status |
|---|---|
| 42 repositories across 11 owners carry the hook files and wrappers | Reported by JFrog; it enumerated the trees |
| 42 developer machines were infected | Not established; no victim count is reported |
| Token-revocation wiper in the npm worm | Reported by SafeDep and JFrog, via The Register |
| cPanel servers were compromised via this operator | Not confirmed |
| Operator is the original Miasma group | Not established; JFrog notes shared tooling only |
We have not run the payload or inspected the blobs ourselves; this post relies on JFrog's, SafeDep's and The Register's published analysis.
Frequently asked questions
What is the testmetest campaign?
It is an npm and GitHub supply-chain campaign that JFrog Security Research tracks as testmetest, a Miasma-family worm descendant that steals credentials and spreads through npm packages. JFrog found it had also armed 42 real GitHub repositories belonging to 11 owners.
How does opening a repository run the malware?
The attacker committed small config files that each point at .github/init.js: a Claude Code SessionStart hook, a Gemini settings hook, Cursor MCP and rules files, and a VS Code task set to run on folder open. Starting a session or opening the folder in that tool executes the file.
Does cloning a repository run the payload?
Cloning alone does not. The trigger is opening the folder in VS Code, starting a Claude Code or Gemini session in it, or loading its Cursor config. The payload is a 12.9 to 13.7 MB Git blob that does nothing until a tool calls it.
Why did nobody notice for weeks?
The payload blobs were too large for GitHub code search to index and for the contents API to return, so early sweeps saw empty files and found 26 repositories before checking trees directly raised the count to 42. The attacker also copied old commit messages and dates to make activity look older.
What should I do if I opened one of these repositories?
Treat the machine as compromised: remove the net-health-checker and pkg-runtime persistence first, then rotate GitHub, npm, cloud, SSH and AI-provider credentials. JFrog warns the worm contains a wiper triggered by token revocation, so order matters.
Can Beam stop this?
Partly. Beam can scan packages before install with scripts disabled and flag risky agent actions through PreToolUse hooks, but a SessionStart or folder-open hook runs before any PreToolUse check, so inspecting repo config files before opening a project is still on you.
Related reading
- npm supply-chain worms are coming for your AI tooling
- Cursor MCP config poisoning
- How to stop Claude Code running dangerous commands
- October 2026 software supply-chain incidents tracker
- Sources: JFrog Security Research, The Register, SafeDep on the June Miasma wave
Details reflect JFrog's October 26, 2026 report and may change as the investigation continues; check the sources for updates.

