Skip to content

Response, New York. Noah Park: Check Point Patches Actively Exploited Management Server Path Traversal. Detection, Dallas. Ava Okello: Report Finds Nearly Half of Deployed Detections Need Attention Before SOCs Can Trust Them.

SOCtember

Always First. Fast SOC News.

JFrog diagram of the testmetest campaign showing three linked areas: npm delivery, where testmetest packages carry a full npm worm that steals credentials; attacker-controlled GitHub, where the CementBeetleSteeple account and GitHub Actions run a publisher; and hijacked third-party GitHub owners and repositories, which received smoke-test placeholders, armed AI and IDE hooks, and shellpanel scanning workflows aimed at CVE-2026-41940, with a decoded ci.js publisher feeding further malicious npm releases back into the cycle.
Diagram: JFrog Security Research, "Beyond testmetest: Native Addon Camouflage and 42 Armed GitHub Repositories Reveal a Hidden npm Publisher," Oct. 26, 2026 (fair use). How JFrog links the npm worm, the publisher and the hijacked repositories.

Threat Intel

JFrog Says the testmetest npm Campaign Armed 42 Real GitHub Repositories With Hooks That Run When Developers Open Them in Claude Code, Gemini, Cursor or VS Code, and Hid Its Newest Payload in a Native Addon

The payload files ran 12.9 to 13.7 MB, too large for GitHub code search to index, so the armed repositories looked clean to anyone searching the usual way. JFrog also found a compiled Node addon that decrypts a hidden .napi_cache file only on real developer machines and stays quiet in CI runners, containers and sandboxes.

Priya Shah, Threat IntelSingapore6 min read

SINGAPORE - The repositories belonged to real people: an Argentine PHP developer with years of genuine commits, a group of students, a legitimate software company. By late September each one carried a few small configuration files telling an AI coding assistant or code editor to run a script the moment the project was opened. The script itself was so large that GitHub's own search could not see it.

JFrog Security Research said on Monday that it had identified 42 armed GitHub repositories across 11 legitimate owners tied to the testmetest npm campaign, which was first documented on the npm side by SafeDep on October 22. JFrog's researchers, Guy Korolevski and Yair Benamou, describe a Shai-Hulud-descendant payload but say the evidence does not establish that the original Miasma operators are responsible. At publication SafeDep had not confirmed third-party victims. JFrog says the operator had in fact already armed real repositories, and that the discrepancy was not about timing: the payloads were built in a way that slipped past the usual search paths.

Hooks that fire when a project is opened

Each armed repository held a hidden .github/init.js plus triggers for several tools: .claude/settings.json with a SessionStart hook that runs node .github/init.js, or bun if node fails; .gemini/settings.json; .cursor/mcp.json and .cursor/rules/setup.mdc; and .vscode/tasks.json for folder-open tasks. Some projects also had package.json, composer.json or Makefile changes. The effect is that routine developer actions, such as opening a workspace in a supported tool, become chances to execute code.

Screenshot of a GitHub code search for the string command node .github/init.js returning 81 files, with results showing .claude/settings.json files in repositories under leamasuero, AsharaFernando18 and joseventura515 that each contain a hooks block running node .github/init.js or bun .github/init.js.
Screenshot: JFrog Security Research, "Beyond testmetest," Oct. 26, 2026 (fair use).GitHub code search surfaced the AI hooks but not the oversized init.js payloads.

JFrog's first sweep with GitHub code search found the hooks in 26 repositories, and the init.js files appeared empty. That was wrong. The wrappers ran 12.9 to 13.7 MB, which GitHub code search did not index and the contents API could not return. Walking each repository through the Git trees and blobs APIs showed that every reachable victim repository was armed, and full account sweeps raised the count to 42. The 10 distinct wrapper files all decoded to the same 2 MB payload, a rebuilt version of the campaign's package publisher; the size differences came from the encoding, not new capability.

The history was doctored. Genuine smoke tests ran in July, but on September 28 the operator armed victim accounts in scripted batches, copying old commit messages and author dates and appending skip-checks:true. One account, created in 2013, had its repositories renamed en masse with the prefix come-to-telegram-rickluminari1--, and 13 of them were armed in a two-minute batch. Four repositories at Crayon-Biz-LLP, a legitimate organization, were armed using at least two member identities.

A publisher that runs inside GitHub Actions

The attacker account CementBeetleSteeple hosted 17 campaign repositories. Several looked normal on their default branches, while short-lived branches named chore/ci- carried a root-level ci.js and a workflow. JFrog recovered 68 pushed branch heads and 16 distinct ci.js builds. Once its guard checks matched the target workflow, the decoded publisher harvested GitHub tokens, inspected workflows for npm trusted-publishing settings, exchanged a GitHub Actions identity token for an npm publishing token, injected a postinstall entry into the target package and tried to publish it.

Screenshot of the GitHub repository CementBeetleSteeple/data-pipeline-testmetest on a branch named chore/ci-d2845bd7, one commit ahead of main, containing a .github/workflows folder and a ci.js file committed with the message chore: update dependencies.
Screenshot: JFrog Security Research, "Beyond testmetest," Oct. 26, 2026 (fair use).A short-lived chore/ci branch carrying ci.js and an Actions workflow.

JFrog is careful about what that proves. A malicious data-pipeline-testmetest@2.0.1 release went out at 11:13:23 UTC on October 21, but no retained Actions run lines up with it, so it was likely published manually with an npm token or from a run that was later deleted.

Some branches also carried a workflow named CodeQL that did no code scanning. It dumped the environment, including NPM_TOKEN, into a one-day artifact called scan-output. JFrog captured 29 of those artifacts before they expired and found three distinct, real-format npm access tokens rotated across six repositories.

An attacker repository, stable-scripts-9965, had become a live data sink. As of October 25 it held 27 data/snap-*.json files totaling 384,825 bytes, committed with the message chore: sync, from October 22 through October 24. JFrog inspected one only to confirm it looked like an encrypted envelope and did not decrypt any.

Screenshot of the CementBeetleSteeple/stable-scripts-9965 repository data folder listing snap-*.json files with the commit message chore: sync, and a file tree including .claude, .cursor, .gemini, .github and .vscode folders.
Screenshot: JFrog Security Research, "Beyond testmetest," Oct. 26, 2026 (fair use).Encrypted snapshot files accumulating in the operator's repository.

The native addon that waits for a real laptop

The newest change moves the malicious logic out of JavaScript. Three packages, @iobandamwer/cache-helper, @iobandamwer/native-helper and path-stream-utils-testmetest, ship a precompiled addon.node, plus addon_win.node for Windows, with a one-line postinstall. The addon reads a dotfile beside it called .napi_cache, XORs every byte with 0x96 and runs the resulting JavaScript with node in a detached process, from /tmp on Linux or as a hidden .nc file in the Windows temp folder.

Before it does, it checks whether anyone is watching. It returns quietly if it sees CI variables such as GITHUB_ACTIONS, GITLAB_CI or plain CI; container markers such as docker, containerd or kubepod; about 1.5 GB of RAM or less or a single CPU; or an attached debugger. The Windows build also stops if Wireshark, Process Monitor, Process Explorer, x64dbg, OllyDbg or Fiddler is running. The result, JFrog says, is a payload aimed at people that sleeps inside the sandboxes automated package scanners use. As of October 26 the campaign's registry inventory spans 25 package names and 82 version records.

JFrog also tied the hijacked accounts to the shellpanel scanner Socket reported on July 22: workflows in the same owner cluster downloaded a binary from 43[.]228[.]157[.]68 to /tmp/.svc and ran it with 1,000 workers and a count of 9,999,999,999 against cPanel ports, with arguments aimed at the cPanel authentication bypass CVE-2026-41940. JFrog found no evidence of successful downstream cPanel compromises.

What SOC and platform teams can do now

  • Audit every repository reachable by your developers' identities for .github/init.js, .claude/settings.json, .gemini/settings.json, .cursor/mcp.json, .cursor/rules/setup.mdc and .vscode/tasks.json that nobody added on purpose, and for commits carrying skip-checks:true. Enumerate Git trees directly; do not rely on code search.
  • Delete unauthorized chore/ci- and deps/upd- branches, disable unknown workflows and remove fake CodeQL artifacts named scan-output.
  • Flag packages whose postinstall loads a native .node addon, especially placeholder or stub packages, and look for a .napi_cache file beside the addon and new dot files in /tmp or the Windows temp folder after npm install.
  • Remove the net-health-checker and pkg-runtime persistence, including LaunchAgents and systemd user services, before revoking stolen GitHub tokens, because the npm worm carries a token-revocation wiper. Then rotate GitHub, npm, cloud, SSH and AI-provider credentials.
  • Block and hunt 43[.]228[.]157[.]68:80, hxxps[:]//aiarmor[.]website/v1/api and hxxps[:]//raw[.]githubusercontent[.]com/CementBeetleSteeple/node-cache-utils/main/loader.js, and search runner logs for /tmp/.svc, ipscan and CVE-2026-41940. Patch cPanel and WHM.

Sources:


Priya Shah covers threat intelligence, intrusion analysis, and adversary tradecraft for SOCtember from Singapore.

Related stories

Threat Intel desk