AntV npm compromise: why trusted packages still broke

A compromised npm maintainer account pushed malicious AntV-linked package versions that stole credentials through install hooks. The key lesson is not just

2026-05-26 GIGATAP Team #security
#npm#supply-chain-security#open-source

What happened#

A compromised npm maintainer account was used on May 19, 2026 to publish malicious versions across a large slice of the AntV ecosystem and related packages, according to Chainguard’s write-up. The attack window was short. Chainguard places the automated publishing waves between roughly 01:39 and 02:18 UTC, with a concentrated burst from 01:56 to 02:06 UTC.

The affected account maintained 547 npm packages. Chainguard says the attacker pushed malicious versions to 314 of them, producing 637 malicious package versions. The affected set included Alibaba’s AntV data visualization packages and widely used standalone packages, including packages with weekly downloads in the hundreds of thousands or above.

Researchers have linked the incident to the broader Mini Shai-Hulud campaign, also associated with earlier compromises involving SAP Cloud Application Programming Model libraries, TanStack, and intercom-client. Chainguard describes this as Shai-Hulud Wave 5, part of a campaign active since September 2025.

This is not a small npm nuisance. Chainguard says the broader Mini Shai-Hulud campaign now covers more than 1,000 malicious versions across over 500 unique packages, spanning npm, PyPI, and Composer. The compromised packages in this AntV-linked event collectively represented about 16 million weekly downloads.

The trick was not the dist-tag#

One important detail: the attacker reportedly did not move the latest dist-tag on most packages. That sounds reassuring, but it does not protect typical installs.

npm semver resolution can still select the highest version matching a declared range. A dependency such as "echarts-for-react": "^3.0.6" can resolve to a malicious version on the next clean install if that malicious version fits the range. Lockfiles reduce that risk only if they are present, respected, and not regenerated into the bad window.

That makes this class of compromise especially dangerous for CI. A developer may see no obvious package name change. A repository may still depend on the same trusted library. The maintainer account is real. The package namespace is real. The malicious version sits inside the normal version stream.

The source of failure is not user gullibility. It is normal package manager behavior meeting a compromised publishing identity.

What the payload did#

Chainguard says every malicious version carried an npm lifecycle hook that executed when npm install ran. The hook launched a roughly 498KB obfuscated Bun script. If Bun was not already installed, the malware attempted to install it silently and then continue execution.

The payload was built for credential theft and propagation. Chainguard reports collection logic for more than 20 credential types, including AWS credentials and metadata sources, Google Cloud, Microsoft Azure, GitHub personal access tokens, npm tokens, SSH keys, Kubernetes service account tokens, HashiCorp Vault tokens, Stripe keys, database connection strings, and local password manager vaults such as 1Password, Bitwarden, pass, and gopass.

The malware also queried cloud metadata endpoints and attempted Docker container escape via the host socket. That matters because many installs happen inside CI runners or developer containers that still have access to sensitive build, deploy, or cloud credentials.

Exfiltration was designed to be hard to read after the fact. Chainguard says stolen data was serialized, compressed, RSA-encrypted, and sent to attacker infrastructure using OpenTelemetry-style request paths to blend into normal-looking traffic. If that description is accurate, network logs may show that data left the environment, but they will not reveal the contents.

There was also a fallback channel. If command-and-control exfiltration failed, the malware could use a stolen GitHub token to create a public repository under the victim’s own account and commit harvested data as JSON. Chainguard notes repositories with the reversed description niagA oG eW ereH :duluH-iahS, meaning “Shai-Hulud: Here We Go Again.”

That fallback is crude, but effective. It turns the victim’s own GitHub account into a dead drop.

Why signatures alone were not enough#

A subset of compromised packages also forged SLSA provenance attestations using Sigstore when running in a CI environment with an available OIDC token, according to Chainguard.

This is the most uncomfortable part of the incident. Signature checks answer a narrow question: was this artifact signed through an expected mechanism? They do not, by themselves, prove the release intent was legitimate or that the code path was clean before signing.

If an attacker controls the publish path and can run inside a trusted CI context, the resulting artifact may carry legitimate-looking provenance. That does not make Sigstore useless. It means provenance needs to be tied to policy, review, source expectations, build isolation, and anomaly detection. A valid signature is evidence. It is not a full trust model.

The same applies to dependency paths. Chainguard reports that many malicious versions injected an entry pointing to an orphan commit in the legitimate GitHub repository, delivering a second copy of the payload through a trusted-looking route. Scanners that only inspect selected package fields or published tarballs can miss that kind of move.

The broader lesson is plain: supply-chain controls fail when they treat familiar names as proof.

What to check now#

Chainguard’s recommended stance is severe: if an environment installed any package maintained by the compromised account with a resolved version published between 01:39 and 02:18 UTC on May 19, 2026, treat that environment as fully compromised.

That is the right bias. This payload targeted credentials, cloud metadata, CI secrets, GitHub tokens, npm tokens, Kubernetes material, and local developer secrets. If the install context could read it, assume the malware may have taken it.

Immediate checks:

  • Audit lockfiles and dependency manifests for packages from the affected atool-maintained package set. Chainguard notes that public advisories contain the full package list.
  • Identify installs or CI runs that resolved package versions published during the May 19 attack window.
  • Rotate credentials reachable from affected developer machines and CI runners: cloud keys, GitHub tokens, npm tokens, SSH keys, kubeconfig material, Vault tokens, Stripe keys, database credentials, and other deploy secrets.
  • Check GitHub accounts and organizations for unexpected public repositories, especially repositories with the reversed Shai-Hulud description. Preserve contents for forensics before deletion.
  • Review network logs for unexpected calls to cloud metadata endpoints from non-cloud contexts and suspicious OpenTelemetry-like egress from install environments.
  • Review GitHub activity for unexpected repository creation, unusual token use, and Sigstore signing operations that do not map to real releases.

For future installs, npm install --ignore-scripts can reduce exposure to lifecycle-hook execution, but it is not a universal fix. Some legitimate packages depend on install scripts. The control is useful when it is part of a deliberate build policy, not when it is pasted in as a panic switch.

The practical takeaway#

This incident is a clean example of why dependency trust must be runtime-aware. The package name was familiar. The maintainer account was real. In some cases, signing signals could appear valid. The failure happened when install-time execution gave a newly published package immediate access to secrets.

The best defenses are boring and architectural: pinning, lockfile discipline, restricted CI credentials, blocked install scripts where possible, short-lived tokens, egress controls, secret scanning, and package ingestion policies that add delay or review before new versions enter production builds.

Chainguard says its customers were protected because its repository cooldown policy blocked the malicious versions during the attack window, and because Chainguard builds from source without install-time scripts. That is also the broader point. The safest system is not the one that guesses every bad package instantly. It is the one that limits what a new package version can do before anyone has had time to inspect it.