testmetest npm campaign planted Claude, Gemini, Cursor, VS Code hooks in 42 hijacked GitHub repos, per JFrog
Summary
JFrog Security Research says the operator of the testmetest npm worm campaign armed 42 third-party GitHub repositories belonging to 11 legitimate owners. The repositories carry execution hooks in the project config files that AI coding assistants and editors read: .claude/settings.json, .gemini/settings.json, .cursor/mcp.json, .cursor/rules/setup.mdc and .vscode/tasks.json. Each hook runs a 12.9 to 13.7 MB file, .github/init.js, which JFrog says decodes to the same hidden payload, a publisher built to push more malicious npm releases. JFrog says this design turns routine developer actions, "including opening a workspace with a supported tool, into payload execution opportunities." SafeDep first documented the npm packages on Oct 22, 2026 and said then that it had found no compromised third-party packages or victims. JFrog says the payload files were large enough to slip past GitHub code search. None of the three sources we read says how many developers opened an armed repository.
Severity
Timeline
JFrog says real smoke-test waves hit hijacked accounts on Jul 23 and Jul 30, 2026.
JFrog says the operator ran scripted arming batches across victim accounts, often copying old commit messages and author dates from legitimate history and appending skip-checks:true.
The worm release data-pipeline-testmetest 2.0.1 is published to npm, per JFrog.
SafeDep publishes its analysis of the testmetest packages. It says it has found no compromised third-party packages or victims and that "All observed activity belongs to the operator's own test accounts."
Per JFrog, a commit adds Claude, Gemini, Cursor and VS Code execution hooks together with .github/init.js to the attacker-owned repository CementBeetleSteeple/stable-scripts-9965.
JFrog publishes its report on the 42 armed repositories. SafeDep updates its post: the operator has moved to a third npm account and a postinstall script that loads a native Node.js addon.
The Register reports JFrog's findings.
SafeDep updates its post again: a fourth account published @testsecuiitytestme/cache-helper 1.0.1 to 1.0.3, and the addon now starts the worm when an application imports the package, "so --ignore-scripts does not protect you."
How it happened
The campaign has an npm half and a GitHub half. On npm, JFrog says malicious testmetest releases ran code at install time through a binding.gyp command, without a conventional install script, and that the full worm "collected credentials, poisoned repositories and CI configuration, and propagated through package registries." SafeDep lists what the worm commits to victims' GitHub and GitLab repositories: a fake CodeQL workflow, AI agent files (.claude/settings.json, .cursor/mcp.json, CLAUDE.md, AGENTS.md, .vscode/tasks.json) and the loader .github/init.js. It says injected commits copy the author and message of the most recent real commit on the branch. The agent files are the trigger. JFrog shows the Claude hook: a SessionStart hook with matcher "*" that runs "node .github/init.js || bun .github/init.js", so the payload starts when an agent session opens in the repository. JFrog says equivalent triggers were planted for Gemini, Cursor MCP configuration and VS Code folder-open tasks, and that the payload launches detached with its output ignored "so AI-session and folder-open hooks do not block." Across the 42 repositories JFrog found 10 distinct wrapper files that all decrypt to a byte-identical 2 MB payload, a rebuild of the operator's ci.js publisher. JFrog says that publisher harvests GitHub tokens and tries to rebuild and publish a weaponized npm package through GitHub Actions. Size kept the payload out of sight. JFrog's first code search found the hooks in 26 repositories, but the .github/init.js files looked empty: "GitHub code search did not index the 12.9 to 13.7 MB blobs, and the contents API could not return files of that size." Listing each repository's Git tree raised the total to 42 armed repositories across 11 owners. JFrog describes the owners as established developers, students and a legitimate organization whose accounts or member credentials were hijacked, and in one case it says the most likely explanation is stolen collaborator access. They are victims of the campaign. JFrog says it found shared tooling with earlier worms but did not establish that the original Miasma operators are responsible.
How to check your app
- Before you open an unfamiliar or newly cloned repository in Claude Code, Gemini, Cursor or VS Code, read its agent and editor config: .claude/settings.json (look for hooks such as SessionStart), .gemini/settings.json, .cursor/mcp.json, .cursor/rules/ and .vscode/tasks.json, CLAUDE.md, AGENTS.md. A command you didn't write, especially one that runs a file under .github/, is a reason to stop.
- Don't accept a tool's prompt to trust a folder, run its tasks or enable its MCP servers until you have read those files. In VS Code, keep unknown folders in Restricted Mode until you have checked them. Cursor's documentation says its workspace trust is off by default and suggests that for untrusted repos you "use a basic text editor instead."
- Look for an unusually large .github/init.js. JFrog's were 12.9 to 13.7 MB, and it warns that GitHub code search and the contents API can miss them, so we suggest checking a local clone, for example with git ls-tree --full-tree -r -l HEAD .github.
- In repositories you own or maintain, JFrog suggests auditing for those files, commits marked skip-checks:true, unexpected chore/ci-* and deps/upd-* branches, and a workflow named CodeQL that uploads an artifact called scan-output. SafeDep adds commits whose message starts with AutoSyncComplete: and new repositories described as project-utils.
- If a machine installed a malicious testmetest version, JFrog says to treat it as a host compromise and remove the net-health-checker and pkg-runtime persistence before revoking the stolen GitHub token, because the worm carries a token-revocation wiper. Then rotate GitHub, npm, cloud, SSH and AI-provider credentials.
- Don't rely on --ignore-scripts alone. SafeDep says the @testsecuiitytestme/cache-helper builds published Oct 27, 2026 start the worm when an application imports the package.
Sources
- JFrog Security Research (Guy Korolevski, Yair Benamou): Beyond testmetest: Native Addon Camouflage and 42 Armed GitHub Repositories Reveal a Hidden npm Publisher (Oct 26, 2026). Primary source
- SafeDep (Kunal Singh): testmetest npm Packages Carry a Miasma-Family Worm (Oct 22, 2026, updated Oct 26 and Oct 27)
- The Register (Jessica Lyons): Worm armed 42 GitHub repos by hiding a payload too big to fail (Oct 26, 2026)
- Cursor docs: Agent Security (workspace trust)
Corrections and updates
Updated at 10:20 a.m. ET to change the severity note. We had written that opening an armed repo "can run the payload with no install step". JFrog calls routine actions such as opening a workspace "payload execution opportunities", and the note now says trusting an armed repository in one of those tools can be enough to start the payload.
Updated at 10:58 a.m. ET on Oct 31 to change the title from "npm worm planted" to "npm campaign planted". JFrog attributes the planted hooks to the campaign: "Within hijacked repositories, the campaign left smoke-test placeholders or armed payloads."
Updated at 8:34 a.m. ET on Nov 1 to add to the severity note and checklist that Cursor's security documentation says workspace trust is "disabled by default", so Cursor may show no workspace trust prompt by default before an armed repository is opened.
Cite this entry
- Title
- testmetest npm campaign planted Claude, Gemini, Cursor, VS Code hooks in 42 hijacked GitHub repos, per JFrog
- Added
- Oct 27, 2026
- URL
- https://vibeincidents.com/incidents/testmetest-npm-worm-ai-agent-hooks-42-repos/
- Suggested citation
VibeIncidents. "testmetest npm campaign planted Claude, Gemini, Cursor, VS Code hooks in 42 hijacked GitHub repos, per JFrog." Oct 27, 2026. https://vibeincidents.com/incidents/testmetest-npm-worm-ai-agent-hooks-42-repos/