The npm worm that has spent the past week republishing itself across hundreds of packages was built to do something researchers have warned about for years: ship a poisoned package carrying a genuine, cryptographically valid provenance record. Palo Alto Networks' Unit 42 took the malware apart and found a routine sitting inside it, waiting for one specific project's release pipeline so it could borrow that project's publishing identity and sign the result so that it verifies.
Unit 42 published its analysis of the worm, which it nicknames ChainDrop, on August 6. IntelFusions covered the first wave of poisoned packages on August 4 and Microsoft's count of more than 400 infected packages a day later. Unit 42 says it found 453 public GitHub repositories across five accounts matching the worm's exfiltration pattern, all since removed, and saw the malware execute in 10 separate environments.
The routine waiting for one repository
Before collecting anything, the worm reads three environment variables, and takes this path only when it is running inside GitHub Actions, in a repository whose name contains opensearch-js, in a workflow whose reference contains release-drafter.yml. There it needs no stolen credential at all. It asks the runner for an OpenID Connect token, a short-lived token that proves which workflow is asking, with the audience set to npm's registry, and trades it at npm's own trusted publishing exchange for a real publish credential. The project's legitimate release identity becomes the attacker's.
What it publishes on that path is deliberately quiet. Rather than the install hook it adds everywhere else, it downloads the latest @opensearch-project/opensearch tarball, bumps the patch version and adds a single dependency named @opensearch/setup, typosquatting the project's own scope and pointing at a pinned commit. In a review diff it reads like an internal helper, and detections built around install hooks would not fire on it.
Then it signs the package: a second OpenID Connect token with a Sigstore audience, a Fulcio certificate, an in-toto SLSA provenance statement over the tarball's hash, an ephemeral signing key, and an entry in the public Rekor transparency log. Unit 42 is blunt about what that means. The attestation is not forged, it is true. The tarball really was built by that workflow in that repository, so if the workflow is running attacker code, a valid signature is exactly what you should expect to see. Pivot on the Rekor log index and the workflow identity inside the certificate, the team says, not on whether the signature checks out. Unit 42 did not observe this routine execute, and it cannot execute anywhere but that one workflow, though it is fully implemented and reachable.
Persistence hidden in developer tooling
Everywhere else, the worm's persistence lives in configuration files few developers ever audit. It writes a .vscode/tasks.json entry labelled "Environment Setup" that runs when the folder is opened, and a .claude/settings.json session-start hook for Claude Code. Each launches a copy of the dropper sitting in the other tool's directory, a naming trick that makes both artifacts look like they belong to something else. Unit 42 lists five files to remove: .claude/math_init.js, .claude/settings.json, .claude/setup.mjs, .vscode/setup.mjs and .vscode/tasks.json. A macOS LaunchAgent and a Linux systemd service are also carried in the sample, but nothing in the code calls them, so treat that as latent capability rather than observed behavior.
On build servers it goes further, using an embedded Python helper to find the GitHub Actions runner process and search its live memory through /proc for identity tokens and runner secrets, catching credentials designed to vanish when a job ends rather than waiting for them to reach disk.
Command and control on a blockchain
ChainDrop carries no hard-coded server address. It queries an Ethereum smart contract to learn where to send stolen data, cycling through roughly 60 public Ethereum RPC endpoints until one answers. The contract emits no events, so the operator rotates domains with a silent state write, which is what happened late on August 4 when a single transaction reconfigured the campaign's infrastructure without any update to the deployed malware. The live domain sits behind a reverse proxy on shared edge addresses, so Unit 42 says to block on the domain or SNI, never on the IP addresses it resolves to.
What to do now
Unit 42's guidance is to find and remove affected package versions, treat any workstation or build runner that installed one as compromised, review recent npm publishing and GitHub repository activity, and revoke and rotate npm, GitHub, cloud, SSH and automation credentials. Remove all five persistence files, and block both exfiltration channels, because blocking the domain still leaves the GitHub path open. Grepping source control for the worm's markers is worthwhile too: they are distinctive enough that a hit is very unlikely to be a false positive.
- Exfiltration endpoint: hxxps://npm-cache[.]com:443/router
- Earlier configured domains: pypi-get[.]com and js-mirror[.]com
- C2 resolver contract: 0xE1f2395ee43e45A1556EC6438a88c31B83493103
- Commit-message marker carrying doubly Base64-encoded stolen tokens: IfYouBlockThisAPIKeyItWillCrashTheLiveProductionServersOfAllThirdPartyClients
- Attacker-created public repositories described "Shai-Hulud: Here We Go Again"
This briefing is provided by IntelFusions for informational and defensive purposes only. It is based on sources assessed to be reliable at the time of writing, and analytic judgments carry the confidence levels indicated. Indicators of compromise are defanged; re-arm them only in controlled environments. IntelFusions is not affiliated with the organizations named and makes no warranty as to completeness or accuracy.