Mini Shai-Hulud Self-Propagating Software Supply Chain Worm
Mini Shai-Hulud is a self-propagating npm/PyPI supply-chain worm. JFrog's May 12 and May 19 updates add a broader count of 170+ npm and 2 PyPI packages, a 323-package @antv wave, and a related @cap-js/openapi 1.4.1 variant.
On this page 0% read
Executive Summary
In late April and May 2026, a self-propagating supply-chain worm designated “Mini Shai-Hulud” hit the npm and PyPI package registries. Attributed to the threat actor group TeamPCP, the worm automates credential harvesting, lateral movement, and package poisoning. The campaign exploits misconfigured CI/CD pipelines (specifically via pull_request_target triggers and GitHub Actions cache poisoning) to steal short-lived OIDC tokens. It then uses these tokens to sign malicious updates with valid SLSA Build Level 3 provenance badges via Sigstore and publish them directly to registries.
New JFrog updates sharpen the current scope. On 2026-05-12, JFrog described an ongoing wave affecting more than 170 npm packages and 2 PyPI packages, totaling more than 200 million weekly downloads JFrog May 12. On 2026-05-19, JFrog analyzed a separate @antv/atool wave and reported 323 legitimate packages compromised through the atool npm maintainer account, plus an additional related @cap-js/openapi@1.4.1 variant using a distinct indirect delivery technique JFrog May 19. Defenders must treat affected systems as fully compromised and immediately rotate all credentials, remove IDE workspace and AI assistant persistence hooks, and configure package managers to ignore install lifecycle scripts.
Key Facts
Threat Type: malicious package, CI/CD compromise, credential theft, self-replicating worm, build provenance failure, artifact tampering
Ecosystem: npm, PyPI
Registry: npm registry, PyPI
Affected Packages:
- @tanstack/react-router
- @tanstack/vue-router
- @tanstack/solid-router
- @tanstack/react-start
- @tanstack/router-core
- @antv/g2
- @antv/g6
- @antv/x6
- @antv/l7
- @antv/s2
- @antv/f2
- echarts-for-react
- timeago.js
- size-sensor
- canvas-nest.js
- @sap/cds
- @sap/cds-dk
- opensearch-py
- lite-llm
- nx-console
Malicious Versions:
- @tanstack/react-router@1.169.5
- @tanstack/react-router@1.169.8
- @tanstack/vue-router@1.169.5
- @tanstack/vue-router@1.169.8
- @tanstack/solid-router@1.169.5
- @tanstack/solid-router@1.169.8
- @tanstack/react-start@1.167.68
- @tanstack/react-start@1.167.71
- @antv/g2@4.2.8
- @antv/g6@4.8.24
- nx-console@18.95.0
- @antv/* published 2026-05-19T01:39:00Z through 2026-05-19T02:06:00Z (639 versions across 323 packages)
- @cap-js/openapi@1.4.1
Fixed Versions:
- nx-console@18.95.1
Safe Versions:
Exposure Window: 2026-04-20 to 2026-05-19T02:06:00Z (specifically May 11, 2026, 19:20–19:26 UTC for TanStack; May 19, 2026, 01:39–02:06 UTC for @antv/atool scope)
Execution Trigger: Install-time execution via preinstall/postinstall scripts (router_init.js or setup.mjs)
Primary Impact: CI/CD & cloud credential theft, lateral self-propagation, development workspace hijack, potential destructive system wipe
Known Iocs:
- filev2.getsession[.]org
- api.masscan[.]cloud
- git-tanstack[.]com
- t.m-kosche[.]com
- ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c
- router_init.js
- setup_bun.js
- bun_environment.js
- transformers.pyz
- gh-token-monitor
Confidence: high
Canonical Source: hxxps://tanstack[.]com/blog/postmortem-cve-2026-45321
Evidence Assessment
- confirmed:
- Incident involving the TanStack npm packages between 19:20 and 19:26 UTC on May 11, 2026, where 84 malicious versions across 42 packages were published via hijacked OIDC tokens TanStack Blog.
- The exploit mechanism chained a
pull_request_targetmisconfiguration, cache poisoning, and memory extraction of GitHub Actions OIDC tokens to publish the packages SentinelOne. - TeamPCP was identified as the threat actor group responsible, utilizing Dune-themed repositories for dead-drop exfiltration of credentials Tenable.
- The malicious payloads exfiltrated highly sensitive developer tokens, cloud credentials (AWS/GCP/Azure), Kubernetes secrets, OIDC tokens, and SSH keys Endor Labs.
- JFrog reports the May 12 wave affected more than 170 npm packages and 2 PyPI packages, with more than 200 million weekly downloads across the affected package set JFrog May 12.
- JFrog reports the May 19 @antv/atool wave compromised 323 legitimate packages and that
@cap-js/openapi@1.4.1carried a related payload variant JFrog May 19.
- likely:
- The execution of Bun runtime smuggling where the malware downloads
setup_bun.jsto bypass traditional Node.js static scanners and run obfuscated payloads under the Bun engine Endor Labs. - The use of OIDC tokens within a trusted runner context to forge valid SLSA Build Level 3 provenance badges through Sigstore, creating cryptographically “verified” malicious releases Wiz.io.
- The execution of Bun runtime smuggling where the malware downloads
- unclear:
- The exact number of downstream developer systems fully wiped by the “dead-man switch” payload (
rm -rf /*) after credential revocation was initiated by security teams Microsoft Threat Intelligence.
- The exact number of downstream developer systems fully wiped by the “dead-man switch” payload (
- not_observed:
- Claims that npm or PyPI registry infrastructure was directly breached; all publishes resulted from OIDC token/credential exfiltration from developer environments and CI/CD pipelines TanStack Blog.
Impact Determination
| Classification | Criteria | Required evidence | Required action | Closure condition |
|---|---|---|---|---|
| Confirmed compromise | a package or release associated with Mini Shai-Hulud is present and package install, import, or build hook executes the worm payload or the reported process, file, or network indicators is observed. | Artifact inventory plus runtime telemetry showing package install, import, or build hook executes the worm payload or listed C2/process/file indicators. | Isolate affected hosts or runners, preserve artifacts, and rotate reachable credentials from a clean environment. | Affected artifacts are removed, exposed credentials are replaced, and downstream audit modules show no suspicious follow-on use. |
| Presumed exposed | a package or release associated with Mini Shai-Hulud was installed, pulled, imported, built, or executed during the exposure window, but telemetry cannot prove exfiltration. | Lockfile, package cache, workflow, image pull, extension inventory, build log, or deployment record tied to the exposure window. | Rebuild from clean artifacts and rotate credentials available to the affected environment. | Credential owners confirm revocation of old material and clean artifacts are deployed. |
| Potentially exposed | The package, workflow, image, extension, or module appears in dependency or deployment records, but package install, import, or build execution is not established. | Manifest, lockfile, build, deployment, or endpoint records plus a named telemetry gap. | Collect the missing execution and telemetry evidence before narrowing scope. | Every hit is dispositioned as confirmed compromise, presumed exposed, or not exposed. |
| Not exposed | No affected version, artifact, mutable reference, or indicator appears in source, lockfiles, build outputs, deployments, package caches, or runtime telemetry. | Repository search, dependency inventory, build/deployment export, package cache query, and runtime telemetry query results. | Preserve the negative search output and keep the prevention controls active. | Search evidence covers developer endpoints, CI runners, production deployments, and package or image caches. |
| Unknown | Required inventory, build, endpoint, network, or audit telemetry is unavailable. | A gap statement naming unavailable systems, owners, and time windows. | Keep the asset in scope and make conservative rotation or rebuild decisions for high-value environments. | The missing evidence is recovered or the risk owner accepts residual uncertainty. |
Minimum Evidence To Collect
Minimum Evidence:
- Dependency, workflow, extension, image, or module inventory covering developer endpoints, CI runners, and production deployments.
- Positive or negative search results for campaign-specific malicious releases.
- Execution evidence for package install, import, or build hook executes the worm payload.
- Process, file, DNS, proxy, firewall, or package-manager telemetry for listed indicators.
- Inventory of credentials, tokens, deployment paths, and downstream systems reachable from exposed environments.
Timeline
- 2026-04-20T00:00:00Z First anomalous publishes identified in the npm registry targeting enterprise developer utilities (specifically SAP developer packages) Endor Labs.
- 2026-04-24T00:00:00Z Endor Labs identifies the initial wave, tracing it to stolen npm publishing credentials. The malware payload is observed downloading the Bun runtime to evade standard analysis tools Endor Labs.
- 2026-05-10T00:00:00Z The worm spreads aggressively to high-profile developer packages including
@tanstack,@antv, and SDKs under Mistral AI, UiPath, and OpenSearch Tenable. - 2026-05-11T19:20:00Z Attacker publishes 84 malicious versions across 42
@tanstack/*packages on npm TanStack Blog. - 2026-05-11T19:26:00Z Publishing of malicious
@tanstackversions completes TanStack Blog. - 2026-05-12T00:00:00Z Wiz and Palo Alto Networks disclose that the malware is utilizing GitHub Actions cache poisoning and forging Sigstore provenance signatures to bypass CI/CD security filters Wiz.io.
- 2026-05-19T00:00:00Z Microsoft and Zscaler publish detailed hunting guides for persistent IDE hooks and LaunchAgents deployed by the worm Microsoft Threat Intelligence.
- 2026-05-19T01:39:00Z Attacker compromises the
atoolnpm maintainer account (the account that manages publishing rights for the entire@antvnpm scope) and begins publishing malicious versions across the @antv namespace Cremit.io. - 2026-05-19T02:06:00Z The @antv publishing blitz ends: 639 malicious package versions published across 323 unique npm packages in 27 minutes Cremit.io. Preinstall hooks invoking
bun run index.jsdeliver a 498 KB obfuscated payload that harvests credentials from 130+ local file paths and establishes dead-drop exfiltration toantvis/G2repository branches via the GitHub API, witht.m-kosche[.]comas fallback C2 Chainguard StepSecurity. - 2026-05-19: JFrog publishes its @antv follow-up and adds
@cap-js/openapi@1.4.1as a related variant with a distinct indirect delivery technique JFrog May 19. - 2026-05-23T01:00:00Z Nx Console release 18.95.1 is published to patch downstream contamination resulting from the compromise of a contributor’s hijacked token SentinelOne.
What Happened
In late April 2026, the TeamPCP threat actor group launched the “Mini Shai-Hulud” supply chain campaign, targeting widely used npm and PyPI developer dependencies Endor Labs. The campaign escalated dramatically on May 11, 2026, when the worm compromised the @tanstack npm scope, publishing 84 poisoned versions of 42 libraries, including @tanstack/router and @tanstack/react-query TanStack Blog.
Instead of stealing static npm credentials, the worm targeted the repository’s GitHub Actions pipeline SentinelOne. By submitting a malicious pull request to the TanStack/router repository, the worm triggered a misconfigured pull_request_target workflow SentinelOne. Due to a cache poisoning vulnerability where fork and base workflows shared execution caches, the attacker poisoned the base branch’s cache SentinelOne. When the trusted base workflow executed, it read the poisoned cache, allowing the worm to inject malicious code directly into the runner environment SentinelOne.
The worm then extracted the runner’s OpenID Connect (OIDC) token from active system memory Wiz.io. This short-lived OIDC token is trusted by the npm registry as a federated publisher identity Wiz.io. Using this token, the attacker signed the malicious release with Sigstore, obtaining a valid SLSA Build Level 3 provenance badge Wiz.io. This caused automated compliance tools and security scanners to trust the artifact’s lineage since the signature and provenance matched the official, legitimate GitHub Actions pipeline Wiz.io.
Once published, any developer executing npm install for the affected packages fell victim Endor Labs. The package lifecycle hooks executed a script that scraped host memory, environment variables, local cloud configurations, and personal access tokens Endor Labs. These stolen secrets were exfiltrated back to TeamPCP by creating public, encrypted repositories on the developer’s own hijacked GitHub account, named with distinct Dune-themed concepts to avoid detection Tenable.
Technical Analysis
Initial Access
The primary vector for initial access in the high-profile TanStack breach was the exploitation of a pull_request_target misconfiguration combined with GitHub Actions cache poisoning SentinelOne. An external pull request triggered the workflow in a context that had access to elevated repository secrets or OIDC trusted-publishing privileges SentinelOne. The attacker exploited a weakness where the dependency or build cache was shared between untrusted forks and trusted base runs, enabling cache poisoning SentinelOne.
Package or Artifact Manipulation
Once execution within the runner was achieved, the worm injected the script router_init.js (for npm) or setup.mjs directly into the package structure before release generation TanStack Blog. In the PyPI ecosystem, a similar payload named transformers.pyz was added to standard Python wheels Endor Labs. The attacker manipulated the package manifests (package.json) to register these files as preinstall or postinstall lifecycle hooks Endor Labs.
Execution Trigger
When downstream developers ran dependency installation commands like npm install or pip install, the package manager automatically executed the lifecycle script under administrative or user privileges Endor Labs. In Python environments, the payload triggered during wheel unpacking or package import time Endor Labs.
Payload Behavior
The malware checks the host environment to determine its runtime:
- Bun Runtime Smuggling: It searches for the
bunbinary. If missing, it downloads a lightweight standalone Bun engine (setup_bun.js) from an attacker-controlled staging server Endor Labs. The main payload is executed inside Bun rather than Node.js, successfully bypassing security monitors that hook only Node.js processes or analyze standard V8 engine calls Endor Labs. - Credential Harvesting: The script scans the system for cloud credentials (AWS keys, Google Cloud JSON files, Azure profiles), container keys (Kubernetes secrets),
.npmrcregistry publishing tokens, SSH private keys, and environment variables Endor Labs. - IDE and Coding Assistant Hijacking: To establish deep local persistence, the worm targets developer tools Microsoft Threat Intelligence. It appends malicious run tasks to the developer’s
.vscode/tasks.jsonfile so that opening the workspace triggers secret exfiltration Microsoft Threat Intelligence. It also places a wrapper inside.claude/settings.jsonto intercept inputs and commands passed to AI coding tools, monitoring for project structure and secrets Microsoft Threat Intelligence. [1] - OS Persistence: It installs a background service named
gh-token-monitorvia a plist daemon in macOS (~/Library/LaunchAgents/) or a systemd unit in Linux to intercept newly generated session tokens Microsoft Threat Intelligence. - Dead-Man Switch / Anti-Analysis Wipe: If the payload detects a sandbox environment (e.g., standard VM analysis indicators) or if a query to its C2 reveals that the stolen publishing token has been revoked, it launches a destructive system command (
rm -rf /*) to destroy evidence and disrupt incident response Microsoft Threat Intelligence. [1]
Exfiltration / C2
Domains:
- filev2.getsession[.]org
- api.masscan[.]cloud
- git-tanstack[.]com
- t.m-kosche[.]com
Ips:
- N/A
Urls:
Protocols:
- HTTPS
- DNS
Endpoints:
- /upload
- /ping
Confidence: high
Propagation
The worm is self-propagating Tenable. Upon harvesting npm and PyPI publishing tokens from compromised workstations or CI runners, it sends them back to the C2 or uses them locally to identify other repositories accessible to that developer Tenable. The malware automatically modifies those downstream packages, signs them, and publishes infected versions to the registry under the developer’s identity, continuing its exponential spread across the supply chain Tenable. [1]
Obfuscation or Evasion
In addition to Bun runtime smuggling, the worm hides its payload using complex XOR obfuscation and packs its Python components into a zipapp (transformers.pyz) Endor Labs. The use of forged Sigstore certificates allows it to bypass cryptographic strictness policies that require verified build provenance Wiz.io.
Worm Self-Propagation Lifecycle
The following architectural flowchart details the self-replicating loop utilized by the TeamPCP worm to propagate through GitHub, package registries, and developer workstations:
graph TD
classDef attacker fill:#f96,stroke:#333,stroke-width:2px;
classDef target fill:#9cf,stroke:#333,stroke-width:2px;
classDef victim fill:#fcf,stroke:#333,stroke-width:2px;
classDef sigstore fill:#ff9,stroke:#333,stroke-width:2px;
Attacker[1. TeamPCP Attacker]:::attacker
TargetRepo[2. Target Repository <br/> e.g. TanStack / antv]:::target
ForkPR[3. Malicious PR from Fork]:::target
GHActions[4. GitHub Actions Runner]:::target
Sigstore[5. Sigstore OIDC Signer]:::sigstore
NPMPyPI[6. Package Registry npm / PyPI]:::sigstore
DevWorkstation[7. Victim Developer Workstation]:::victim
Attacker -- "Submits PR" --> TargetRepo
TargetRepo -- "Triggers pull_request_target" --> ForkPR
ForkPR -- "Cache Poisoning Exploit" --> GHActions
GHActions -- "Steals OIDC Token" --> Sigstore
Sigstore -- "Signs Poisoned Artifact" --> NPMPyPI
NPMPyPI -- "npm/pip install" --> DevWorkstation
subgraph Workstation Compromise
DevWorkstation -- "1. Scrapes Tokens & Keys" --> LocalSecrets[Stolen npm/PyPI & Github Tokens]
DevWorkstation -- "2. Local Persistence" --> IDE[IDE vscode/tasks.json & LaunchAgents]
DevWorkstation -- "3. Worm Execution" --> Propagation[Propagation Module]
end
Propagation -- "Injects Malicious Updates" --> TargetRepo
LocalSecrets -- "Exfiltrated to" --> Attacker
Affected Assets and Blast Radius
Affected Assets:
- ecosystems: npm,PyPI
- packages: @tanstack/router,@tanstack/react-query,@tanstack/store,@antv/g2,@antv/g6,@sap/cds,@sap/cds-dk,opensearch-py,lite-llm,nx-console
- versions: @tanstack/* published on May 11, 2026,nx-console@18.95.0
- repositories: github.com/TanStack/router,github.com/antvis/*,github.com/nrwl/nx-console
- container_images: N/A
- CI_CD_systems: GitHub Actions
- developer_tools: VS Code,Claude Code
- environments: developer workstations,CI runners,build pipelines,containers,production systems
Credentials At Risk:
- npm tokens
- GitHub tokens
- cloud credentials
- SSH keys
- environment variables
Not Currently Known To Affect:
- Non-NodeJS/Non-Python development environments lacking Bun and Python command-line tools.
Indicators of Compromise
The following indicators of compromise (IOCs) can be used to scope exposure across local repositories, systems, and telemetry exports:
Package Versions
- @tanstack/react-router@1.169.5
- @tanstack/react-router@1.169.8
- @tanstack/vue-router@1.169.5
- @tanstack/vue-router@1.169.8
- @tanstack/solid-router@1.169.5
- @tanstack/solid-router@1.169.8
- @tanstack/react-start@1.167.68
- @tanstack/react-start@1.167.71
- @antv/g2@4.2.8
- @antv/g6@4.8.24
- nx-console@18.95.0
- @antv/* published 2026-05-19T01:39:00
Files
- router_init.js
- setup_bun.js
- bun_environment.js
- transformers.pyz
- gh-token-monitor
Hashes
- ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c
Domains
- filev2[.]getsession[.]org
- api[.]masscan[.]cloud
- git-tanstack[.]com
- t[.]m-kosche[.]com
- www[.]endorlabs[.]com
- www[.]microsoft[.]com
- www[.]sentinelone[.]com
Urls
- hxxps://filev2[.]getsession[.]org/upload
- hxxps://api[.]masscan[.]cloud/ping
- hxxps://www[.]endorlabs[.]com/blog/mini-shai-hulud-npm-worm-hits-sap-developer-packages
- hxxps://tanstack[.]com/blog/postmortem-cve-2026-45321
- hxxps://www[.]microsoft[.]com/en-us/security/blog/hunting-the-shai-hulud-supply-chain-worm
- hxxps://www[.]sentinelone[.]com/blog/anatomy-of-cve-2026-45321
Detection and Hunting
Hunt Manifest: mini-shai-hulud-worm-hunt-1
- Title: local repository and exported telemetry scope
- Question: Does the telemetry scope contain patterns associated with Mini Shai-Hulud Self-Propagating Software Supply Chain Worm?
- Telemetry Family: process
- Telemetry Context: host filesystem or log export
- Positive Signal: Indicators of compromise matched in telemetry: local repository and exported telemetry scope
#!/usr/bin/env python3
"""Generic IOC scope scanner for mini-shai-hulud-worm.
Searches repository trees and exported logs for literal IOC values from iocs.json.
Exit codes:
0: no matches
1: one or more indicators matched
2: execution error
"""
import argparse
import fnmatch
import os
import sys
from pathlib import Path
OUT = Path(os.environ.get("OUT", "hp-mini-shai-hulud-worm-ioc-scope"))
CONTENT_INDICATORS = [
"@tanstack/react-router@1.169.5",
"@tanstack/react-router@1.169.8",
"@tanstack/vue-router@1.169.5",
"@tanstack/vue-router@1.169.8",
"@tanstack/solid-router@1.169.5",
"@tanstack/solid-router@1.169.8",
"@tanstack/react-start@1.167.68",
"@tanstack/react-start@1.167.71",
"@antv/g2@4.2.8",
"@antv/g6@4.8.24",
"nx-console@18.95.0",
"@tanstack/router-core@1.169.5",
"@antv/x6@2.2.0",
"@antv/l7@2.19.0",
"@antv/s2@1.30.0",
"@antv/f2@4.1.0",
"echarts-for-react@3.0.0",
"timeago.js@4.0.2",
"size-sensor@1.0.1",
"canvas-nest.js@2.0.4",
"@sap/cds@7.9.2",
"@sap/cds-dk@7.9.2",
"opensearch-py@2.5.0",
"lite-llm@1.34.0",
"ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c",
"filev2.getsession.org",
"api.masscan.cloud",
"git-tanstack.com",
"t.m-kosche.com",
"www.endorlabs.com",
"www.microsoft.com",
"www.sentinelone.com",
"https://filev2.getsession.org/upload",
"https://api.masscan.cloud/ping",
"https://www.endorlabs.com/blog/mini-shai-hulud-npm-worm-hits-sap-developer-packages",
"https://tanstack.com/blog/postmortem-cve-2026-45321",
"https://www.microsoft.com/en-us/security/blog/hunting-the-shai-hulud-supply-chain-worm",
"https://www.sentinelone.com/blog/anatomy-of-cve-2026-45321",
"@tanstack/react-router",
"1.169.5",
"1.169.8",
"@tanstack/vue-router",
"@tanstack/solid-router",
"@tanstack/react-start",
"1.167.68",
"1.167.71",
"@antv/g2",
"4.2.8",
"@antv/g6",
"4.8.24",
"nx-console",
"18.95.0",
"@tanstack/router-core",
"@antv/x6",
"2.2.0",
"@antv/l7",
"2.19.0",
"@antv/s2",
"1.30.0",
"@antv/f2",
"4.1.0",
"echarts-for-react",
"3.0.0",
"timeago.js",
"4.0.2",
"size-sensor",
"1.0.1",
"canvas-nest.js",
"2.0.4",
"@sap/cds",
"7.9.2",
"@sap/cds-dk",
"opensearch-py",
"2.5.0",
"lite-llm",
"1.34.0"
]
PATH_INDICATORS = [
"router_init.js",
"setup_bun.js",
"bun_environment.js",
"transformers.pyz",
"gh-token-monitor"
]
EXCLUDE_DIRS = {".git", "node_modules", "vendor", "dist", "build", ".venv", "__pycache__"}
def _iter_files(root):
root = Path(root)
if not root.exists():
return
if root.is_file():
yield root
return
for current, dirs, files in os.walk(root):
dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS]
for name in files:
yield Path(current) / name
def _path_matches(path):
text = str(path)
matches = []
for indicator in PATH_INDICATORS:
if not indicator:
continue
if indicator.startswith(("/", "~")):
candidate = Path(os.path.expanduser(indicator))
if candidate.exists() and path == candidate:
matches.append(indicator)
if indicator in text or fnmatch.fnmatch(text, indicator) or fnmatch.fnmatch(path.name, indicator):
matches.append(indicator)
return matches
def _content_matches(path):
try:
content = path.read_text(errors="ignore")
except Exception:
return []
return [indicator for indicator in CONTENT_INDICATORS if indicator and indicator in content]
def _scan_roots(roots):
matches = []
for root in roots:
if not root:
continue
for path in _iter_files(root):
for indicator in _path_matches(path):
matches.append(f"{path}: path matched {indicator!r}")
for indicator in _content_matches(path):
matches.append(f"{path}: content matched {indicator!r}")
return matches
def main():
parser = argparse.ArgumentParser(description="Scan files and logs for Halting Problems IOC values")
parser.add_argument("roots", nargs="*", default=["."], help="File or directory roots to scan")
parser.add_argument("--log-root", default=os.environ.get("LOG_ROOT", ""), help="Optional exported log directory")
args = parser.parse_args()
OUT.mkdir(parents=True, exist_ok=True)
indicator_lines = sorted(set(CONTENT_INDICATORS + PATH_INDICATORS))
(OUT / "ioc-indicators.txt").write_text("\n".join(indicator_lines) + "\n")
roots = list(args.roots)
if args.log_root:
roots.append(args.log_root)
matches = _scan_roots(roots)
if matches:
(OUT / "ioc-scope-matches.txt").write_text("\n".join(matches) + "\n")
print(f"[!] Found {len(matches)} IOC matches; details written under {OUT}")
return 1
print(f"[+] No IOC matches found; indicator inventory written under {OUT}")
return 0
if __name__ == "__main__":
try:
sys.exit(main())
except Exception as exc:
print(f"[-] Execution failure: {exc}", file=sys.stderr)
sys.exit(2)
Hunt Manifest: mini-shai-hulud-worm-hunt-2
- Title: local repository and exported telemetry scope
- Question: Does the telemetry scope contain patterns associated with Mini Shai-Hulud Self-Propagating Software Supply Chain Worm?
- Telemetry Family: file
- Telemetry Context: host filesystem or log export
- Positive Signal: Indicators of compromise matched in telemetry: local repository and exported telemetry scope
#!/usr/bin/env python3
"""Generic IOC scope scanner for mini-shai-hulud-worm.
Searches repository trees and exported logs for literal IOC values from iocs.json.
Exit codes:
0: no matches
1: one or more indicators matched
2: execution error
"""
import argparse
import fnmatch
import os
import sys
from pathlib import Path
OUT = Path(os.environ.get("OUT", "hp-mini-shai-hulud-worm-ioc-scope"))
CONTENT_INDICATORS = [
"@tanstack/react-router@1.169.5",
"@tanstack/react-router@1.169.8",
"@tanstack/vue-router@1.169.5",
"@tanstack/vue-router@1.169.8",
"@tanstack/solid-router@1.169.5",
"@tanstack/solid-router@1.169.8",
"@tanstack/react-start@1.167.68",
"@tanstack/react-start@1.167.71",
"@antv/g2@4.2.8",
"@antv/g6@4.8.24",
"nx-console@18.95.0",
"@tanstack/router-core@1.169.5",
"@antv/x6@2.2.0",
"@antv/l7@2.19.0",
"@antv/s2@1.30.0",
"@antv/f2@4.1.0",
"echarts-for-react@3.0.0",
"timeago.js@4.0.2",
"size-sensor@1.0.1",
"canvas-nest.js@2.0.4",
"@sap/cds@7.9.2",
"@sap/cds-dk@7.9.2",
"opensearch-py@2.5.0",
"lite-llm@1.34.0",
"ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c",
"filev2.getsession.org",
"api.masscan.cloud",
"git-tanstack.com",
"t.m-kosche.com",
"www.endorlabs.com",
"www.microsoft.com",
"www.sentinelone.com",
"https://filev2.getsession.org/upload",
"https://api.masscan.cloud/ping",
"https://www.endorlabs.com/blog/mini-shai-hulud-npm-worm-hits-sap-developer-packages",
"https://tanstack.com/blog/postmortem-cve-2026-45321",
"https://www.microsoft.com/en-us/security/blog/hunting-the-shai-hulud-supply-chain-worm",
"https://www.sentinelone.com/blog/anatomy-of-cve-2026-45321",
"@tanstack/react-router",
"1.169.5",
"1.169.8",
"@tanstack/vue-router",
"@tanstack/solid-router",
"@tanstack/react-start",
"1.167.68",
"1.167.71",
"@antv/g2",
"4.2.8",
"@antv/g6",
"4.8.24",
"nx-console",
"18.95.0",
"@tanstack/router-core",
"@antv/x6",
"2.2.0",
"@antv/l7",
"2.19.0",
"@antv/s2",
"1.30.0",
"@antv/f2",
"4.1.0",
"echarts-for-react",
"3.0.0",
"timeago.js",
"4.0.2",
"size-sensor",
"1.0.1",
"canvas-nest.js",
"2.0.4",
"@sap/cds",
"7.9.2",
"@sap/cds-dk",
"opensearch-py",
"2.5.0",
"lite-llm",
"1.34.0"
]
PATH_INDICATORS = [
"router_init.js",
"setup_bun.js",
"bun_environment.js",
"transformers.pyz",
"gh-token-monitor"
]
EXCLUDE_DIRS = {".git", "node_modules", "vendor", "dist", "build", ".venv", "__pycache__"}
def _iter_files(root):
root = Path(root)
if not root.exists():
return
if root.is_file():
yield root
return
for current, dirs, files in os.walk(root):
dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS]
for name in files:
yield Path(current) / name
def _path_matches(path):
text = str(path)
matches = []
for indicator in PATH_INDICATORS:
if not indicator:
continue
if indicator.startswith(("/", "~")):
candidate = Path(os.path.expanduser(indicator))
if candidate.exists() and path == candidate:
matches.append(indicator)
if indicator in text or fnmatch.fnmatch(text, indicator) or fnmatch.fnmatch(path.name, indicator):
matches.append(indicator)
return matches
def _content_matches(path):
try:
content = path.read_text(errors="ignore")
except Exception:
return []
return [indicator for indicator in CONTENT_INDICATORS if indicator and indicator in content]
def _scan_roots(roots):
matches = []
for root in roots:
if not root:
continue
for path in _iter_files(root):
for indicator in _path_matches(path):
matches.append(f"{path}: path matched {indicator!r}")
for indicator in _content_matches(path):
matches.append(f"{path}: content matched {indicator!r}")
return matches
def main():
parser = argparse.ArgumentParser(description="Scan files and logs for Halting Problems IOC values")
parser.add_argument("roots", nargs="*", default=["."], help="File or directory roots to scan")
parser.add_argument("--log-root", default=os.environ.get("LOG_ROOT", ""), help="Optional exported log directory")
args = parser.parse_args()
OUT.mkdir(parents=True, exist_ok=True)
indicator_lines = sorted(set(CONTENT_INDICATORS + PATH_INDICATORS))
(OUT / "ioc-indicators.txt").write_text("\n".join(indicator_lines) + "\n")
roots = list(args.roots)
if args.log_root:
roots.append(args.log_root)
matches = _scan_roots(roots)
if matches:
(OUT / "ioc-scope-matches.txt").write_text("\n".join(matches) + "\n")
print(f"[!] Found {len(matches)} IOC matches; details written under {OUT}")
return 1
print(f"[+] No IOC matches found; indicator inventory written under {OUT}")
return 0
if __name__ == "__main__":
try:
sys.exit(main())
except Exception as exc:
print(f"[-] Execution failure: {exc}", file=sys.stderr)
sys.exit(2)
Downstream Abuse Audits
Compromised workstations expose active API credentials, requiring immediate rotated revocation. The following platforms are at risk:
- GitHub OIDC and PATs: Attackers harvested SSH private keys and Git Personal Access Tokens. Auditors must inspect recent action runs and release logs during the exposure window.
- Cloud IAM Credentials: AWS, Azure, and GCP session tokens. CloudTrail and Activity Logs should be queried for AssumeRole or write operations originating from unexpected IP addresses.
- NPM and Package Registries: Publishing tokens and credentials. Registry profiles must be audited for unauthorized version publishes or token additions.
Sources
- TanStack Blog. Role: DIRECT_SOURCE Impact: Detailed explanation of the TanStack compromise, the exact exposure window (19:20 - 19:26 UTC), affected packages, and the OIDC exploitation chain.
- SentinelOne. Role: PRIMARY_RESEARCH Impact: Deep technical walkthrough of the cache-poisoning exploit, the
pull_request_targetmisconfiguration, and downstream Nx Console compromise. - Endor Labs. Role: PRIMARY_RESEARCH Impact: Initial discovery of the worm’s SAP CAP targets, Bun runtime smuggling, and Python
transformers.pyzpayload. - Wiz.io. Role: PRIMARY_RESEARCH Impact: Analysis of the Sigstore SLSA Build Level 3 provenance forgery and the federation bypass.
- Tenable. Role: SECONDARY_ANALYSIS Impact: Discussion of TeamPCP’s Dune-themed indicators, exfiltration repositories, and worm-like lateral movement.
- Microsoft Threat Intelligence. Role: PRIMARY_RESEARCH Impact: Identification of the persistent IDE task hooks, macOS/Linux LaunchAgent services, and the credential revocation dead-man switch behavior.
- Aikido Security. Role: SECONDARY_ANALYSIS Impact: Overall synthesis of the massive May 2026 supply chain wave and remediation recommendations.
- Orca Security. Role: PRIMARY_RESEARCH Impact: Technical analysis of the 323-package @antv namespace compromise, 27-minute attack window, preinstall hook execution via Bun, credential harvesting from 130+ file paths.
- Palo Alto Networks Unit42. Role: PRIMARY_RESEARCH Impact: Attribution to TeamPCP Mini Shai-Hulud campaign, correlation with prior waves, worm propagation analysis.
- Chainguard. Role: PRIMARY_RESEARCH Impact: Payload analysis: 498 KB obfuscated JS, Bun execution, antvis/G2 dead-drop via GitHub API, t[.]m-kosche[.]com fallback C2.
- StepSecurity. Role: DIRECT_SOURCE Impact: Dead-drop commit analysis in antvis/G2 repo, 130+ credential file paths targeted, GitHub API exfiltration mechanism.
- Cremit.io. Role: SECONDARY_ANALYSIS Impact: 01:39–02:06 UTC attack window timing, 639 malicious versions across 323 packages, package list.
- JFrog: Shai-Hulud, Here We Go Again. Role: PRIMARY_RESEARCH Impact: Broader May 12 campaign scope across 170+ npm packages and 2 PyPI packages with 200M+ weekly downloads, credential theft, encrypted exfiltration, and destructive dead-man switch behavior.
- JFrog: Shai-Hulud Returns: npm Worm hits @antv. Role: PRIMARY_RESEARCH Impact: Confirms 323 legitimate packages in the @antv/atool wave and identifies
@cap-js/openapi@1.4.1as a related payload variant.
IOC Clipboard
16 IOCstransformers.pyz transformers[.]pyz filev2.getsession.org filev2[.]getsession[.]org api.masscan.cloud api[.]masscan[.]cloud git-tanstack.com git-tanstack[.]com t.m-kosche.com t[.]m-kosche[.]com www.endorlabs.com www[.]endorlabs[.]com www.microsoft.com www[.]microsoft[.]com www.sentinelone.com www[.]sentinelone[.]com tanstack.com tanstack[.]com https://filev2.getsession.org/upload hxxps://filev2[.]getsession[.]org/upload https://api.masscan.cloud/ping hxxps://api[.]masscan[.]cloud/ping https://www.endorlabs.com/blog/mini-shai-hulud-npm-worm-hits-sap-developer-packages hxxps://www[.]endorlabs[.]com/blog/mini-shai-hulud-npm-worm-hits-sap-developer-packages https://tanstack.com/blog/postmortem-cve-2026-45321 hxxps://tanstack[.]com/blog/postmortem-cve-2026-45321 https://www.microsoft.com/en-us/security/blog/hunting-the-shai-hulud-supply-chain-worm hxxps://www[.]microsoft[.]com/en-us/security/blog/hunting-the-shai-hulud-supply-chain-worm https://www.sentinelone.com/blog/anatomy-of-cve-2026-45321 hxxps://www[.]sentinelone[.]com/blog/anatomy-of-cve-2026-45321 ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c