JFrog Security Research has published a teardown of IronWorm, a self-replicating npm supply-chain worm that takes the Shai-Hulud playbook and rebuilds it as a heavy, Rust-compiled implant. It hides behind an eBPF kernel rootkit, runs its command channel over Tor, and sweeps a developer’s machine for every credential it can reach. The campaign infected 36 npm packages before it was caught and largely scrubbed — but the cleanup was incomplete, and the operator’s own mistakes suggest this was a rehearsal, not the finished product.
What happened
The trail started with the npm account asteroiddao, tied to the asteroid-dao GitHub org in the Arweave/WeaveDB decentralized-database ecosystem. Every package on the account had been republished inside one narrow window, each shipping a native binary that ran from an install hook. Following [email protected], JFrog found a tarball with four clean files copied from the real SDK and a fifth — a 976 KB Linux ELF tucked into a tools/setup directory. The package.json wired it to a preinstall hook, which runs before npm resolves dependencies. Type npm install and the binary executes: no build step, no click, no warning.
IronWorm spreads the way Shai-Hulud does — by turning stolen credentials into propagation. In a CI environment it abuses npm’s Trusted Publishing OIDC flow: it requests an OIDC identity token, exchanges it at npm’s /-/npm/v1/oidc/token/exchange/package/<pkg> endpoint for a short-lived package-scoped token, and publishes a trojanized release with no stored npm credential ever touched. It commits itself back into GitHub repos under forged identities — a binary dropper attributed to claude, or a secrets-stealing GitHub Actions workflow attributed to dependabot, renovate, or github-actions. Commit timestamps are copied from each repo’s last real commit, so malicious changes appear years old. JFrog traced 57 backdated commits across nine organizations.
The technical details
This is not an off-the-shelf stealer. The ELF is packed with a modified UPX stub (the UPX! magic overwritten to break signature detection), then compiled as a large Rust release build with per-call-site string encryption — no single key unlocks everything. The credential sweep is exhaustive: 86 environment variables (cloud providers, object storage, databases, SCM and registry tokens, CI/CD, Vault, Kubernetes, and 14 AI/ML keys including Anthropic, OpenAI, Gemini, Mistral, and xAI) plus 20+ credential files — ~/.aws/credentials, ~/.kube/config, ~/.docker/config.json, and newer targets like ~/.claude/.credentials.json, ~/.codex/auth.json, and ~/Cursor/auth.json. A dedicated module injects a JavaScript hook into the Exodus desktop wallet — weakening Electron’s webSecurity, sandbox, contextIsolation, and nodeIntegration — to capture the password and seed mnemonic. Another, running inside a Kubernetes pod, reads the service-account token, walks namespaces, dumps every reachable Secret, and logs into Vault if it finds one.
The stealth layer is an embedded eBPF rootkit. It rewrites /proc listings to hide PIDs from ps/top, auto-hides processes matching a watchlist on every execve, answers ptrace attempts with SIGKILL (so strace can kill your shell), and filters /proc/net/tcp and netlink to hide its sockets. C2 is plain HTTP wrapped in a Tor circuit, beaconing to /api/agent for commands: upload secrets, drop files, or open a remote shell.
Why it was caught — and why that’s cold comfort
The operator made errors. The eBPF object shipped with .BTF.ext debug metadata intact, handing JFrog 214 verbatim source lines. And the credential scanner carries a hardcoded skip-list containing one 12-word BIP-39 recovery phrase in plaintext — the operator’s own wallet, so the malware wouldn’t rob him during testing (0x7e28D9889f414B06c19a22A9Bd316f0AC279a4d6, a near-empty dust wallet). The rootkit’s strongest tricks also rely on a BPF helper that’s restricted under kernel lockdown, so on hardened systems the hidden processes and sockets reappear.
What to do right now
If you pull anything from the Arweave/WeaveDB ecosystem or the asteroiddao account, treat your environment as compromised. Audit every repo the account could write to for backdated commits, unexpected build hooks (tools/setup, .github/scripts/precheck), and workflow changes attributed to claude, dependabot[bot], renovate[bot], or github-actions[bot] outside their normal context — JFrog’s IoC list names all 36 packages. Deprecate malicious versions, publish clean releases with advisories, and rotate every key and secret the account could reach: cloud creds, registry and SCM tokens, SSH/GPG keys, AI API keys, and Kubernetes service-account tokens. Enable kernel lockdown on build hosts to blunt the rootkit, pin GitHub Actions to trusted SHAs you control, and turn on 2FA across npm and GitHub. As always, the cheapest defense against a preinstall worm is to install dependencies with scripts disabled (npm install --ignore-scripts) in untrusted contexts.
Source: JFrog Security Research — Iron worm: Shai-Hulud’s rustier cousin