IronWorm: a Rust npm worm that abuses npm Trusted Publishing, vanishes behind an eBPF rootkit and exfiltrates without C2
6 June 2026. JFrog has documented a new, self-replicating npm worm — IronWorm, „Shai-Hulud's rustier cousin“ — that directly defeats three of my standard supply-chain recommendations: it self-publishes in CI via npm's Trusted-Publishing OIDC flow (no stored token), exfiltrates without external C2 through a swapped GitHub Actions workflow, and hides on the host behind an eBPF kernel rootkit. The Rust ELF runs via a preinstall hook, harvests 86 environment variables including ~/.claude/.credentials.json, AWS, Docker, Kubernetes and Vault access, and commits itself as author „claude“. This is not the Miasma/binding.gyp wave from 4 June, but a separate campaign — and it targets exactly the defence-in-depth that was recommended after Shai-Hulud.
TL;DR — 90 seconds
Affected?
Projects/CI pulling a trojanised version of one of the packages re-published by the compromised npm account asteroiddao (WeaveDB/Arweave ecosystem; JFrog lists the full IoC package list, e.g. weavedb-sdk@0.45.3). Aggravating: developer and CI machines with reachable cloud/registry/AI credentials.
Risk?
Credential theft (86 environment variables incl. 14 AI provider keys, plus ~/.claude/.credentials.json, ~/.aws/credentials, ~/.kube/config, ~/.docker/config.json, Vault, Kubernetes secrets), CI/CD self-publication via npm Trusted Publishing, GitHub worm propagation with forged bot commits, eBPF rootkit concealment, Tor C2.
Immediate action?
Check builds/CI against the JFrog IoC list, enforce --ignore-scripts, rotate affected credentials, check CI workflows for swapped secret-exfil jobs, review npm Trusted Publishing permissions.
Recommendation?
Mid-market and enterprise: scope and monitor Trusted Publishing per package, verify GitHub Actions workflow integrity, audit commit-author identities (claude, dependabot[bot], renovate[bot], github-actions[bot]) outside their normal context, consider kernel lockdown on build runners.
Criticality?
high (references the hero badge — active, caught-in-the-wild worm; audit within the 48h window).
What is the problem?
According to JFrog (as of 3 June 2026), the find began with a suspicious npm account, asteroiddao (linked to the GitHub organisation asteroid-dao in the Arweave/WeaveDB ecosystem): every one of the account's packages had been re-published within a narrow window, each new version shipping a native binary that ran from an install hook. The tarball — using weavedb-sdk@0.45.3 as the example — held four clean original files and a fifth: a roughly 976 KB Linux ELF under tools/setup. The package.json gave away the trick:
preinstall runs before npm even resolves dependencies — npm install is enough, no build step, no click. So far this sounds like classic install-hook abuse. What sets IronWorm apart is what the binary is and what it does next.
The binary is a Rust release build (UPX-packed with an overwritten UPX magic as an anti-unpack trick, per-call-site string encryption with no global key) — deliberately painful to reverse. JFrog calls IronWorm „Shai-Hulud's rustier cousin“: the same core idea as the Shai-Hulud worm (compromise developers, steal credentials, spread on through trusted supply-chain workflows, even the same commit names), but „taken to the next level“.
Three of those levels are the real reason for this article — because they defeat exactly the defence-in-depth recommended after the recent npm worm waves (including by me): self-publication via npm Trusted Publishing instead of a stolen token, exfiltration without external C2 via GitHub Actions artifacts, and host concealment via an eBPF kernel rootkit. The three mechanisms are described in detail further down (section „What I actually did“ → A closer look).
Who is affected?
Affected
Not affected
Conditions / aggravating
Projects/CI pulling a trojanised version of one of the packages re-published by asteroiddao (WeaveDB/Arweave family; full list in the JFrog IoC section)
Projects without any affected version in the dependency tree (direct or transitive)
npm install runs without --ignore-scripts; the preinstall hook launches the Rust ELF from tools/setup
Developer machines and CI pipelines with reachable credentials
Pure runtime environments with no npm install step and no reachable secrets
86 environment variables incl. 14 AI provider keys; file paths such as ~/.claude/.credentials.json, ~/.codex/auth.json, ~/Cursor/auth.json, ~/.gemini/settings.json, ~/.aws/credentials, ~/.kube/config, ~/.docker/config.json
GitHub organisations the compromised account could write to — their repos receive back-dated malicious commits
Repos the compromised account had no write access to
57 back-dated commits across nine organisations; commit author forged as claude or bot identities (dependabot, renovate, github-actions)
CI environments with npm Trusted Publishing for affected packages
Packages without a Trusted-Publishing trust relationship for the compromised account
Self-publication runs via the OIDC token exchange — without a stored npm token
JFrog marked the malicious versions as deprecated within a day; many malicious commits were „quietly“ removed. The honest caveat from the source matters: the compromised account was highly active that month (around 4,500 contributions to private projects), so the visible public activity may be only part of the picture. The authoritative, continuously maintained package/IoC list is at JFrog; we deliberately avoid presenting a snapshot here as complete.
Impact
The point is not „yet another npm worm“, but that IronWorm attacks three layers that were considered the answer after Shai-Hulud.
First, the token layer. The standard recommendation after the recent waves was: move away from long-lived npm publish tokens towards short-lived, CI-bound credentials (OIDC, Trusted Publishing). IronWorm turns exactly that around: in CI it requests an OIDC identity token itself and exchanges it at npm's Trusted-Publishing endpoint for a short-lived, package-scoped automation token — without ever touching a stored credential — and publishes the trojanised version with it. The measure meant to eliminate long-lived tokens becomes the publication path.
Second, the network/C2 layer. Anyone trying to detect worms via outbound C2 connections finds nothing here: if a repo has GitHub Actions, IronWorm replaces an existing workflow with one that writes ${{ toJSON(secrets) }} into an innocuously named file and uploads it as a build artifact — retrievable by anyone with access. No external C2. The actions are pinned to real commit SHAs, which ironically makes the workflow look more security-conscious; job and step names come from a pool of plausible CI phrases. (JFrog did not observe this second path triggering in the wild, but the logic is fully present and functional.)
Third, the host/detection layer. An eBPF kernel rootkit hides processes (/proc listings are rewritten → invisible to ps, top, ls), hides TCP connections (/proc/net/tcp and netlink filtered → invisible to ss), and answers ptrace attempts on protected processes with SIGKILL (an strace can kill your own shell). The good news for operators: the two strongest tricks — process and socket concealment — hang on a BPF helper that modifies memory in the calling process. On hardened systems with kernel lockdown that capability is restricted; the rewrites fail quietly, and the supposedly hidden processes and sockets become visible again. The rootkit then loses its most effective concealment.
There is no CVE; it is an ongoing supply-chain incident, not a single product flaw. The operational severity is high nonetheless: pulling an affected version potentially loses the keys to your entire cloud, registry and AI-tool landscape — and the worm is built to turn that theft into self-propagation.
Mitigation / immediate actions
Note: the following steps are my operational recommendation based on the technique documented by JFrog — not a vendor-certified procedure.
Operational Decision Block
Act now if … one of the JFrog-listed asteroiddao packages (WeaveDB/Arweave family) is in your package-lock.json / CI cache, directly or transitively.
Check as a priority if … your CI uses npm Trusted Publishing or has cloud/AI credentials within reach.
Awareness only if … you pull none of the affected versions and already enforce --ignore-scripts in CI.
Step 1 — Secure installs
# disable preinstall/native-hook execution globally (CI and local)
npm config set ignore-scripts true
# or per run:
npm ci --ignore-scripts
# Note: stops the preinstall trigger, NOT the GitHub-side worm propagation.
Step 2 — Check the dependency tree against the JFrog IoC list
# examples from the JFrog IoC list; reconcile with the full, current list at the source
grep -nE "weavedb-sdk|weavedb-client|weavedb-node-client|aonote|wao|arnext|roidjs|zkjson|fpjson-lang" package-lock.json
npm ls weavedb-sdk weavedb-client wao arnext 2>/dev/null
Step 3 — Rotate credentials
If an affected version was installed (locally or in CI), treat as compromised:
- rotate npm, GitHub (incl. PATs), cloud tokens (AWS/GCP/Azure), Vault and Kubernetes tokens
- rotate AI provider keys (Anthropic/OpenAI/Gemini/Cohere/Mistral/Groq/Perplexity/xAI)
- assume local credential files are exposed:
~/.claude/.credentials.json, ~/.codex/auth.json, ~/Cursor/auth.json,
~/.gemini/settings.json, ~/.aws/credentials, ~/.kube/config, ~/.docker/config.json
Step 4 — Check CI workflows and Trusted Publishing
# find swapped secret-exfil workflows (toJSON(secrets) -> file -> upload-artifact)
grep -RInE "toJSON\(secrets\)|upload-artifact" .github/workflows/
# review unexpected workflow changes in the git history
git log --oneline -- .github/workflows/
# npm Trusted Publishing: which packages trust which CI identity? scope tightly.
Detection / verification
IOCs derived directly from the technique documented by JFrog.
Forged/back-dated commits and bot identities
# commits attributed to "claude" (forged AI-assistant identity)
git log --all --pretty='%H %an <%ae> %ad %s' | grep -iE "claude@users\.noreply\.github\.com"
# back-dated commits: author date well before commit date (manipulated timestamp)
git log --all --format='%ci | %ai | %an | %s' | awk -F' \\| ' '{ if (substr($1,1,4) != substr($2,1,4)) print }'
# suspicious "routine" commit messages from the JFrog list
git log --all --oneline | grep -iE "resolve lint warnings|sync lockfile|update workflow configuration"
Unexpected build hooks and payload paths
# native payload paths IronWorm prefers
find . -path '*/tools/setup' -o -path '*/.github/scripts/precheck' 2>/dev/null
# preinstall hooks that launch a local binary
grep -RInE '"preinstall"\s*:\s*"\./' --include=package.json .
Expose the eBPF rootkit's concealment
Process/socket concealment hangs on a BPF memory helper that is restricted under
kernel lockdown. Check points:
- check kernel lockdown status: cat /sys/kernel/security/lockdown
- under active lockdown, previously hidden processes/sockets become visible again
- ptrace anomaly: an strace/gdb that suddenly kills the target shell via SIGKILL
is itself an indicator (the rootkit's anti-debug logic)
- unusual Tor activity from build runners / dev machines
Operator guidance
Mittelstand
Contain first, then rotate. Check lockfiles and CI caches against the JFrog IoC list; if a version is in there, treat every token reachable in the pipeline as compromised — including the AI provider keys and the local ~/.claude/~/.codex/~/.aws files that many do not think of first when they hear „credential rotation“. Enforce --ignore-scripts in CI. Important: that stops the preinstall trigger, not the GitHub-side propagation — so check your workflows as well.
Enterprise
In addition: scope npm Trusted Publishing tightly per package and monitor token-exchange activity — IronWorm shows that OIDC Trusted Publishing is a publication capability that can be abused without a human token. Treat GitHub Actions workflow files as protected artifacts (required reviews, branch protection, CODEOWNERS) and alert on diffs that introduce toJSON(secrets) or new upload-artifact steps. Audit commit-author identities (claude, dependabot[bot], renovate[bot], github-actions[bot]) outside their normal context — the disguise lives on no one questioning those names.
Kubernetes / containers
Rebuild build images as soon as an affected version was in the tree. IronWorm explicitly targets the Kubernetes service-account token (reads it, walks the namespaces, dumps reachable Secrets and logs in to a reachable Vault instance with the same token). Keep service-account tokens minimally scoped, restrict secret access per workload, and consider kernel lockdown on build runners — it takes the eBPF rootkit's most effective concealment away.
Declarative stacks (NixOS / Talos / Flatcar)
Pin to known-good versions, reproducible rebuild. Talos and Flatcar ship kernel lockdown and a minimised, immutable host surface respectively — exactly the property that breaks the rootkit's concealment here. The advantage of the declarative track remains provability: which npm version flowed into which build and when is demonstrable — which significantly speeds up containing a supply-chain incident.
What I actually did
I treat supply-chain worms as a pipeline-discipline question, not as a single package. For the stacks I operate, that meant, on this occasion: checked lockfiles and caches against the JFrog IoC list, confirmed --ignore-scripts as a CI default, ran a sweep for tools/setup/.github/scripts/precheck payload paths and for claude-attributed and back-dated commits, checked GitHub Actions workflows for the toJSON(secrets) exfil pattern, and reviewed the npm Trusted Publishing scopes of the packages I maintain.
The honest lesson: my own recommendation from the binding.gyp post (short-lived, tightly scoped CI credentials instead of long-lived tokens) still holds — but IronWorm shows it is not enough. Replacing long-lived tokens with OIDC/Trusted Publishing closes one door and you have to watch the next: the Trusted-Publishing capability itself. So I added two things: first, workflow integrity as its own control (required reviews + alerting on secret serialisation in workflows); second, kernel lockdown on build runners where the host allows it.
A closer look: the three mechanisms that set IronWorm apart from last week
1 — Self-publication via npm Trusted Publishing (the OIDC twist). Instead of using a stolen npm token, IronWorm itself requests an OIDC identity token in a CI environment with the audience parameter expected for Trusted Publishing, submits it to npm's endpoint /-/npm/v1/oidc/token/exchange/package/<pkg> and receives a short-lived, package-scoped automation token. With it, it publishes the trojanised version like a legitimate release. The kicker: Trusted Publishing was introduced to make stored tokens unnecessary — so IronWorm no longer needs to steal a stored credential at all; it inherits the CI's trust relationship. The control here is not „remove the token“ but „who/what may trigger the token exchange in this CI context at all“ — i.e. scope and monitoring of the Trusted-Publishing relationship itself.
2 — Exfiltration without external C2 (GitHub Actions as the delivery channel). If a repo already has workflows, the worm replaces an existing file (from a list of common workflow names) with a job that serialises ${{ toJSON(secrets) }} into an innocuously named file (format-results.txt) and uploads it via actions/upload-artifact. Every part is a legitimate GitHub Actions feature turned against the repo owner: toJSON(secrets) serialises all secrets available to the run; the upload makes them retrievable as an artifact; no external C2 traffic occurs. Disguise included: the actions are pinned to real commit SHAs (looks more security-conscious than tags), job/step names sound routine („Run checks“, „Collect metrics“), and the commit comes from bot identities like dependabot/renovate/github-actions. The elegant pairing: a manifest change looks right coming from „claude“; a workflow change looks right coming from a bot. (JFrog did not observe this second path triggering in the wild — but the logic is complete and functional.)
3 — eBPF kernel rootkit (clever concealment, sloppy mistake). The worm carries an embedded BPF object (compiled with clang 22.1.5). Crucially, the .BTF.ext debug metadata was not stripped, so JFrog could reconstruct 214 source lines and all 10 BPF maps — a rare look into the hidden layer. The rootkit hides processes (rewritten /proc listings), hides TCP connections (/proc/net/tcp + netlink filtering), automatically adds matches of a name watchlist to the hidden set on every execve, and answers ptrace on protected processes with SIGKILL. But the two strongest tricks (process/socket concealment) hang on a BPF helper that writes memory in the calling process — under kernel lockdown that is restricted, the rewrites fail quietly, and hidden things become visible again. What remains is anti-debug and part of the network concealment. Practical consequence: a hardened, locked-down kernel is not a nice-to-have here but actively breaks the concealment.
Attribution aside. The operator hard-coded his own 12-word BIP-39 recovery phrase as a 74-byte entry in a skip list of the wallet stealer, so the stealer would not rob him — and thereby shipped it to everyone who unpacks the binary (wallet 0x7e28…, near-empty). Together with the unstripped BPF debug metadata, that fits JFrog's assessment: a carefully built but still unfinished implant — „perhaps the rehearsal, not the final form“. Notable for my running thread: while Project Glasswing / Claude Mythos delivers findings on the defender side, IronWorm abuses the name „claude“ as attacker camouflage — the same brand, two sides of the wall.
Frequently asked questions about IronWorm
Is IronWorm the same as last week's binding.gyp/Miasma worm?+
No. Per JFrog, IronWorm is a separate campaign — a Rust infostealer/worm via the npm account asteroiddao (WeaveDB/Arweave ecosystem), with an eBPF rootkit, Trusted-Publishing self-publication and C2-less exfiltration. The binding.gyp/„Phantom Gyp“ Miasma worm (which we covered on 4 June) is a parallel, technically different campaign. Both belong to the Shai-Hulud family, but they are not identical.
Doesn't npm Trusted Publishing / OIDC protect against stolen tokens?+
Against long-lived stolen tokens, yes — but IronWorm bypasses that by requesting an OIDC token itself in CI and exchanging it at the endpoint /-/npm/v1/oidc/token/exchange/package/<pkg> for a short-lived, package-scoped automation token. It needs no stored credential. The control is therefore scope and monitoring of the Trusted-Publishing relationship, not just „no long-lived tokens“.
How does the worm exfiltrate without an external C2 server?+
It replaces an existing GitHub Actions workflow with a job that writes ${{ toJSON(secrets) }} into an innocuously named file and uploads it via actions/upload-artifact — retrievable by anyone with access. No outbound C2 traffic. Check: grep -RInE "toJSON\(secrets\)|upload-artifact" .github/workflows/ plus the workflow git history.
Does kernel lockdown really help against the eBPF rootkit?+
Partly, and at the most effective point. Process and socket concealment hang on a BPF helper that modifies memory in the calling process; under active kernel lockdown that is restricted, the rewrites fail quietly, and hidden processes/sockets become visible again (cat /sys/kernel/security/lockdown). Anti-debug logic and part of the network concealment remain, but the strongest stealth falls away.
Why do malicious commits appear as author „claude“ or as dependabot/renovate?+
Disguise. A new build hook in a manifest looks „right“ when it comes from an AI assistant (claude@users.noreply.github.com); a workflow change looks „right“ when it comes from a bot. On top of that, commits are back-dated (the timestamp of the last real commit is copied) so they appear years old. Per JFrog, the real author was the account ocrybit.
Is there a CVE or a patch?+
No. This is an ongoing supply-chain campaign, not a single product flaw — so no CVE, no vendor patch. The „fix“ is operational: avoid affected versions (JFrog IoC list), secure installs (--ignore-scripts), rotate exposed credentials incl. AI keys, check CI workflows and Trusted-Publishing scopes, harden build runners.
Conclusion
IronWorm is no sensation in its fundamentals — a preinstall hook, a credential sweep, GitHub self-propagation are familiar from Shai-Hulud and the Miasma/binding.gyp wave. What is new is that it attacks the answers to them: Trusted Publishing instead of a stolen token, GitHub artifacts instead of C2, an eBPF rootkit instead of noisy persistence. For me the lesson is not „the recommendations were wrong“ but „one layer is not enough“: whoever replaces long-lived tokens with OIDC must scope and monitor the Trusted-Publishing relationship; whoever relies on C2 detection must check workflow integrity; whoever trusts host telemetry benefits from kernel lockdown. Because the incident is ongoing, the package list is governed by the JFrog source, not by a snapshot. Don't dramatise, but check today — specifically the controls that were considered „done“ after the last wave.
Before the next install harvests your CI and AI keys — let's talk about your pipeline discipline.
I check, mitigate and validate your npm/CI supply chain against worms like IronWorm — including Trusted-Publishing scope and workflow integrity.
Dependency and lockfile audit against the IoC list, --ignore-scripts enforcement, rotation of exposed cloud and AI credentials, npm Trusted-Publishing scoping and monitoring, GitHub Actions workflow-integrity checks (required reviews, alerting on toJSON(secrets)), and hardening of build runners incl. kernel lockdown.
Platform operations instead of advice-on-paper: I check, mitigate and validate production pipelines — from the SBOM inventory through the stopgap measure to validation.
Programming since 2002 – self-taught, set up my own business with KO-Web in 2012. Over 100 projects, with a focus on security, performance, automation and quality. Today freelance: DevSecOps consulting, training and software development.