Keyv 及其好友在 Shai-Hulud 供应链攻击中遭到入侵
Keyv and friends compromised in active Shai-Hulud supply chain attack

原始链接: https://www.aikido.dev/blog/keyv-and-friends-compromised-in-npm-supply-chain-attack

2026年8月4日,一起重大供应链攻击事件导致多个常用 npm 包(包括 `keyv`、`flat-cache` 和 `file-entry-cache`)的关键维护者 GitHub 账户被攻破。攻击者通过直接向主分支推送恶意代码,发布了带有有效来源签名(provenance signatures)的受损版本,影响了超过 868 个软件包,这些软件包的月下载量总计达数十亿次。 此次攻击表现为一种自我传播的蠕虫病毒。一旦受害者运行 `npm install`,一个经过混淆的投放脚本(`setup.mjs`)就会执行并安装 Bun 运行时,随后运行有效载荷(`Math_Symbol.js`)。该有效载荷旨在积极窃取敏感数据,包括: * **令牌与密钥:** npm、GitHub、AWS、Stripe、Slack 和 HashiCorp Vault 的凭据。 * **基础设施访问权限:** Kubernetes 机密信息和本地环境变量。 * **系统文件:** SSH 密钥、私钥、`.env` 文件和 VPN 配置。 该蠕虫还会针对其他维护者进行感染扩散。强烈建议用户检查其依赖项、执行安全扫描,并对这一针对软件供应链的自动化、高影响威胁保持警惕。

一项被称为“Shai-Hulud”的活跃供应链攻击已波及超过 2,000 个 NPM 软件包,影响了每月数十亿次的安装。该恶意软件利用 `preinstall` 脚本执行一个经过高度混淆的释放程序(`setup.mjs`),进而下载并运行恶意负载(`Math_Symbol.js`)。此负载旨在窃取敏感的环境密钥,通过公开的 GitHub 仓库泄露这些数据,并进一步感染其他开发者的机器。 此次攻击在 Hacker News 上引发了关于软件安全的热烈讨论,讨论的主要要点包括: * **缓解策略:** 专家建议实施“依赖冷却期”(在采用新版本前等待几天)、使用不具备发布权限的隔离 CI/CD 工作流,以及禁用自动依赖更新。 * **系统性弱点:** 评论者强调了安装过程中执行任意代码的危险,以及 JavaScript 生态系统中过度依赖所固有的风险。 * **安全工具:** 讨论强调了纵深防御的重要性,提倡使用诸如用于密钥注入的本地代理、静态/动态分析工具(如 Packj),以及严格隔离构建与测试环境。 * **注册中心责任:** 人们对 NPM 和 GitHub 等主要注册中心仍未针对此类广泛的自动化攻击实施更强大、更主动的检测表示强烈不满。
相关文章

原文

On August 4, 2026, attackers compromised the GitHub account of the maintainer behind keyv, a key-value storage library with roughly 127 million weekly npm downloads, and used that access to inject credential-stealing malware across the entire package family. The same maintainer owns cacheable (29M downloads/month), flat-cache (565M downloads/month), file-entry-cache (557M downloads/month), and several other widely-used caching utilities, all of which were swept up in the same attack. The compromise was carried out by pushing malicious files directly to the main branch and then immediately cutting a new release, meaning the poisoned versions were published to npm with valid provenance signed by GitHub Actions.

The compromised packages include:

  • keyv (619M monthly downloads)
  • cacheable (29M/month)
  • cache-manager (16M/month)
  • cacheable-request (133M/month)
  • flat-cache (565M/month)
  • file-entry-cache (557M/month)
  • @cacheable/node-cache (5.9M/month)
  • @cacheable/memory (28M/month)
  • @cacheable/utils (34M/month)
  • ecto (4K/month)

We are also also seeing very active community spread of this supply chain worm to other maintainers and packages, including major organisations:

  • @deliveroo/reevent 1.0.1
  • @or-sdk/invitations 1.4.9
  • @picsart/ai-sdk 3.32.2
  • @qlik/embed-runtime 1.6.4
  • picasso.js 2.11.6

Update — August 4, 2026, 13:37 CEST: At least 868 packages (across 1381 versions) have been compromised by the worm, with a combined total of over 2 billion monthly installs at the time of writing.

What happened

Every package in the family received two new files, setup.mjs and Math_Symbol.js, along with a "preinstall": "node setup.mjs" entry added to each package.json. Anyone who ran npm install against an affected version would have had setup.mjs execute automatically before their install completed.

setup.mjs is a heavily obfuscated dropper. Its only job is to silently download the Bun JavaScript runtime from github[.]com/oven-sh/bun/releases/download/bun-v1.3.13/ and use it to execute the real payload, Math_Symbol.js:

execFileSync(<bun binary>, ['<script_dir>/Math_Symbol.js'], {
  stdio: 'inherit',
  cwd: <script_dir>
})

The Math_Symbol.js is a heavily obfuscated 728 KB JavaScript file containing credential stealers that harvest secrets from the victim's environment, encrypt the findings, and exfiltrate them to a public GitHub repository whose description reads "Shai-Hulud: Here We Go Again". The payload also contains worm-like propagation functionality to infect packages of other maintainers that have installed one of the compromised packages.

What it steals

The Math_Symbol.js file implements a set of credential extractors, each targeting a different secret store on the victim machine.

npm tokens

Reads ~/.npmrc and scans the filesystem for any other .npmrc files. Extracts authToken values and any //registry.*:_authToken=... entries. Validates each token live against registry.npmjs[.]org/-/whoami before exfiltrating.

GitHub tokens

Three token formats are targeted: classic PATs (ghp_...) and OAuth tokens (gho_...), GitHub App server-to-server tokens (ghs_...), and JWT OIDC tokens. Sources include ~/.config/gh/hosts.yml, environment variables, and a filesystem scan.

On GitHub Actions runners, the payload also executes a shell command that reads the runner process memory directly to dump the entire secret store. It reads ACTIONS_ID_TOKEN_REQUEST_TOKEN and ACTIONS_ID_TOKEN_REQUEST_URL to steal OIDC tokens used for npm publishing.

AWS credentials

  • ~/.aws/credentials and ~/.aws/config, parsing all named profiles
  • AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN environment variables
  • EC2 Instance Metadata Service at 169.254.169.254, trying IMDSv2 first with a fallback to IMDSv1
  • ECS container metadata endpoint at 169.254.170.2
  • AWS Secrets Manager, calling secretsmanager:ListSecrets across multiple regions to enumerate and exfiltrate all secrets stored there

Kubernetes secrets

Reads the service account token, CA certificate, and namespace from /var/run/secrets/kubernetes.io/serviceaccount/. Uses the service account token to query the Kubernetes API directly and retrieve all secrets in the namespace. Also targets KUBECONFIG and ~/.kube/config.

HashiCorp Vault tokens

Checks six sources in priority order: the VAULT_TOKEN environment variable, ~/.vault-token, the GitHub Actions runner path /home/runner/.vault-token, several well-known container paths, a Kubernetes auth login using the stolen service account JWT, and Vault's AWS IAM auth endpoint using any stolen AWS credentials. After obtaining a token, it enumerates all KV stores via /v1/sys/mounts and reads every secret from KV v1 and v2 paths.

Stripe and Slack tokens

Scans for Stripe API keys (both test and live, sk_ and pk_ prefixes) and Slack tokens (xox[baprs]-...) across all files touched by the filesystem scanner.

Generic filesystem scan

A platform-aware scanner (macOS vs Linux) runs roughly 200 glob patterns across the filesystem, targeting among other things:

  • .env, .env.*, and .envrc files
  • Private key files (*.pem, *.key, *.p12, *.pfx, *.jks)
  • SSH keys and config (id_rsa, id_ed25519, .ssh/config)
  • Terraform state files and .tfvars
  • Docker registry credential files (docker/config.json)
  • KeePass databases (*.kdbx)
  • VPN configs (*.ovpn)
  • IDE config files including .vscode/tasks.json and .claude/settings.json

Files over 5 MB are skipped. Up to 64 concurrent reads are used. A generic regex engine is also applied across all scanned files, flagging PEM private keys, SSH public keys, Azure storage keys, database connection strings with embedded credentials, and generic key=value patterns matching common secret field names.

How Aikido detects this

If you are an Aikido user, check your central feed and filter on malware issues. This will surface as a 100/100 critical issue. Aikido rescans nightly, but we recommend triggering a manual rescan now.

If you are not yet an Aikido user, you can create an account and connect your repos. Our malware coverage is included in the free plan, no credit card required.

For broader coverage across your whole team, Aikido's Device Protection gives you visibility and control over the software packages installed on your team's devices. It covers browser extensions, code libraries, IDE plugins, and build dependencies, all in one place. Stop malware before it gets installed.

For future protection, consider Aikido Safe Chain (open source). Safe Chain sits in your existing workflow, intercepting npm, npx, yarn, pnpm, and pnpx commands and checking packages against Aikido Intel before install.

联系我们 contact @ memedata.com