Wiz Research is actively investigating an ongoing software supply chain attack affecting multiple npm packages in the Keyv/Cacheable ecosystem. This blog presents our initial findings based on the analysis completed so far. As our investigation continues, we will update this post with additional technical details, indicators of compromise, attribution insights, and any newly identified affected packages or infrastructure.
August 4, 2026, 1345 UTC update: Many additional packages have been compromised, see the full list over on our GitHub. Wiz identified the user-agent Bun/1.3.13 performing malicious calls related to this activity, and added it to the IOCs section.
August 4, 2026, 1500 UTC update: Added analysis of the malware's use of an Ethereum smart contract to dynamically retrieve C2 domains.
August 4, 2026, 1525 UTC update: Added prevalence data on compromised packages (to date).
August 4, 2026, 1950 UTC update: Added additional breakdown of the payload.
What happened?
Multiple npm packages in the keyv/cacheable ecosystem were compromised following the compromise of a GitHub maintainer account, resulting in the publication of malicious package versions. All versions shared a consistent payload. Starting at 9:00 UTC, the attacker first used a compromised identity to introduce IDE persistence payloads to the keyv repository, and then shortly after published a new version of keyv containing their payload. This worm has since propagated to over 400 distinct npm packages.
Wiz analysis indicates the payload is a descendant of the "Mini" Shai-Hulud malware family, therefor sharing similarities with the TeamPCP and antv supply chain campaigns.
Payload
This is not an entirely novel payload. It is heavily based on the public "Mini" Shai-Hulud-Open-Source repositories published by TeamPCP, with a few crucial changes.
As in the original open sourced code, the malware targets a broad range of sensitive data, including cloud credentials, infrastructure secrets, developer credentials, AI-related configuration files, and cryptocurrency wallets. It also attempts to harvest secrets from CI/CD environments, identify build runners, and enumerate cloud environments to facilitate credential theft and further compromise. Persistence is attempted via Claude Code hooks and VS Code tasks.json. Data is exfiltrated via GitHub repositories created under compromised identities, using the default description Shai-Hulud: Here We Go Again.
The following changes and new features have been identified:
A new intimidation string is used in the initial commits,
IfYouBlockThisAPIKeyItWillCrashTheLiveProductionServersOfAllThirdPartyClients. On commits introducing IDE hooks to repositories for persistence, a minor change has been made to the commit message, which is nowchore: update config.The operator has provided a new RSA key, used to encrypt exfiltrated data. However, the RSA key used to verify fallback C2s posted to GitHub has not been changed.
The lock file used by the payload has been updated to
tmp.dpkg_14527.lock.The dead-man's switch has been updated to be selectively armed based on response from the C2.
The C2 can respond with a code value that can specify a command to arm the dead-man switch and specify the token that the switch should monitor for revocation. The revocation monitor is set up to look for a valid response from
api.github.com/user, so any type of token that is valid there can be used.
The malware added the feature of sending a unique value for each machine alongside the exfiltrated credentials. This value is a SHA256 hash of a concatenated list of system properties. We believe that this is likely to enable targeted delivery of customized dead-man switch values.
The malware now retrieves its command-and-control (C2) domains from an Ethereum smart contract (
StringListStore) using aneth_call, rather than embedding them in the payload. This allows the operator to update C2 infrastructure without modifying the malware. On-chain history shows the contract was initially configured with three domains before being updated to return onlynpm-cache[.]com. The contract owner was funded by an address that has previously been flagged as associated with scam activity.Targets for credential theft have been expanded by almost 70%, with new additions including AI-agent credential stores for Claude, OpenAI, Codex, Cursor and Gemini; cryptocurrency keystores for Foundry, Solana and Monero; self-hosted CI and registry secrets such as Jenkins'
master.key, Argo CD and Harbor; Alibaba Cloud and Tencent Cloud CLI configurations; and system files including/etc/shadow.
What steps should security teams take?
Identify and remove the affected package versions from development, build, and CI/CD environments.
Treat affected systems as potentially compromised and rebuild them if the malicious packages were installed.
Rotate exposed credentials, including cloud credentials, GitHub tokens, SSH keys, Kubernetes configurations, Terraform credentials, and other developer secrets.
Review cloud and source code environments for unauthorized access or suspicious activity following package installation.
Monitor for the published IOCs, including the identified domains, file artifacts, and other indicators associated with the campaign.
Strengthen software supply chain defenses by enabling dependency allowlisting, package integrity verification, and provenance controls where available.
Affected Packages
Track the full list of impacted packages over on our GitHub.
| Package | Malicious Version |
|---|---|
| @cacheable/utils | 2.5.1 |
| @hubsync/web-sdk-react | 6.3.7 |
| @nebula.js/nucleus | 0.5.1 |
| @ornikar/babel-preset-base | 6.0.3 |
| @ornikar/babel-preset-kitt-universal | 8.0.3 |
| @ornikar/babel-preset-react | 6.1.4 |
| @ornikar/browserslist-config | 8.0.3 |
| @ornikar/commitlint-config | 8.3.2 |
| @ornikar/eslint-config-babel | 24.0.1 |
| @ornikar/eslint-config-react | 24.0.1 |
| @ornikar/eslint-config-typescript | 24.0.1 |
| @ornikar/eslint-config-typescript-react | 24.0.1 |
| @ornikar/eslint-plugin-neverthrow | 1.3.1 |
| @ornikar/eslint-plugin-ornikar | 24.0.1 |
| @ornikar/graphql-config | 1.1.1 |
| @ornikar/intl-config | 10.0.2 |
| @ornikar/kitt2 | 1.0.1 |
| @ornikar/monorepo-config | 14.3.2 |
| @ornikar/postcss-config | 9.1.2 |
| @ornikar/prettier-config | 9.0.3 |
| @ornikar/prismic-components | 0.0.2 |
| @ornikar/react-modern-calendar-datepicker | 3.2.1 |
| @ornikar/react-native-svg-transformer | 1.0.6 |
| @ornikar/renovate-config | 9.0.2 |
| @ornikar/repo-config-react | 13.0.8 |
| @ornikar/repo-config-react-legacy-css | 15.1.2 |
| @ornikar/rollup-plugin-postcss | 2.0.5 |
| @ornikar/stylelint-config | 14.0.3 |
| @ornikar/typed-css-modules-loader | 0.8.2 |
| @qlik/embed-react | 2.5.3 |
| @qlik/embed-runtime | 1.6.4 |
| @qlik/embed-web-components | 1.7.3 |
| @qlik/runtime-module-loader | 1.5.1 |
| @thiennq/docs-viewer | 1.6.2 |
| babel-plugin-linaria-css-to-undefined | 0.3.1 |
| cache-manager | 7.2.10 |
| cacheable-request | 13.0.20 |
| http-metrics-middleware | 2.2.2 |
| keyv | 6.0.0 |
| picasso-plugin-hammer | 2.11.6 |
| picasso-plugin-q | 2.11.6 |
| pob-test-package-in-monorepo | 5.2.1 |
Indicators of Compromise (IOCs)
Domains
npm-cache[.]com- Exfil domain (104.21.35[.]216, Cloudflare)eth-mainnet.nodereal[.]io- ETH RPC (35.175.164[.]77, Amazon)go.getblock[.]io- ETH RPC (185.44.207.215)eth.llamarpc[.]com- ETH RPC (172.67.167.200, Cloudflare)pypi-get[.]comjs-mirror[.]com
User-agents
Bun/1.3.13
File Artifacts
/tmp/bun-dl-*/node_modules/keyv/Math_Symbol.js
| File | Hash |
|---|---|
| Math_Symbol.js | 35a672cf34b996b91f3e1c28cbf3a05a37e036e4 |
| math_init.js | 35a672cf34b996b91f3e1c28cbf3a05a37e036e4 |
| setup.mjs (in .claude) | 686aa40d0fc22c8d569494543a0f891f359f2f99 |
| setup.mjs (in .vscode) | f525d52ceb966516686b482d3dc0137028cc6a63 |
Strings / Attribution Indicators
IfYouBlockThisAPIKeyItWillCrashTheLiveProductionServersOfAllThirdPartyClients