critical Threat analysis

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.

#npm#pypi#supply-chain#worm#teampcp#slsa#credentials-theft
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/[email protected] 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/[email protected]"
      - "@tanstack/[email protected]"
      - "@tanstack/[email protected]"
      - "@tanstack/[email protected]"
      - "@tanstack/[email protected]"
      - "@tanstack/[email protected]"
      - "@tanstack/[email protected]"
      - "@tanstack/[email protected]"
      - "@antv/[email protected]"
      - "@antv/[email protected]"
      - "[email protected]"
      - "@antv/* published 2026-05-19T01:39:00Z through 2026-05-19T02:06:00Z (639 versions across 323 packages)"
      - "@cap-js/[email protected]"
    fixed_versions:
      - "[email protected]"
    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: https://tanstack.com/blog/postmortem-cve-2026-45321

    Source Confidence & Evidence Mapping

    • 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_target misconfiguration, 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/[email protected] carried a related payload variant JFrog May 19.
    • likely:
      • The execution of Bun runtime smuggling where the malware downloads setup_bun.js to 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.
    • 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.
    • 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

    ClassificationCriteriaRequired evidenceRequired actionClosure condition
    Confirmed compromisea 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 exposeda 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 exposedThe 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 exposedNo 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.
    UnknownRequired 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 @tanstack versions 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 atool npm maintainer account (the account that manages publishing rights for the entire @antv npm 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.js deliver a 498 KB obfuscated payload that harvests credentials from 130+ local file paths and establishes dead-drop exfiltration to antvis/G2 repository branches via the GitHub API, with t.m-kosche[.]com as fallback C2 Chainguard StepSecurity.
    • 2026-05-19: JFrog publishes its @antv follow-up and adds @cap-js/[email protected] as 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:

    1. Bun Runtime Smuggling: It searches for the bun binary. 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.
    2. Credential Harvesting: The script scans the system for cloud credentials (AWS keys, Google Cloud JSON files, Azure profiles), container keys (Kubernetes secrets), .npmrc registry publishing tokens, SSH private keys, and environment variables Endor Labs.
    3. 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.json file so that opening the workspace triggers secret exfiltration Microsoft Threat Intelligence. It also places a wrapper inside .claude/settings.json to intercept inputs and commands passed to AI coding tools, monitoring for project structure and secrets Microsoft Threat Intelligence.
    4. OS Persistence: It installs a background service named gh-token-monitor via a plist daemon in macOS (~/Library/LaunchAgents/) or a systemd unit in Linux to intercept newly generated session tokens Microsoft Threat Intelligence.
    5. 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.

    Exfiltration / C2

    domains:
      - "filev2.getsession[.]org"
      - "api.masscan[.]cloud"
      - "git-tanstack[.]com"
      - "t.m-kosche[.]com"
    ips:
      - "N/A"
    urls:
      - "https://filev2.getsession[.]org/upload"
      - "https://api.masscan[.]cloud/ping"
    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.

    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"
        - "[email protected]"
      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

    domains:
      - value: filev2.getsession[.]org
        source: https://www.endorlabs.com/blog/mini-shai-hulud-npm-worm-hits-sap-developer-packages
        confidence: high
      - value: api.masscan[.]cloud
        source: https://www.endorlabs.com/blog/mini-shai-hulud-npm-worm-hits-sap-developer-packages
        confidence: high
      - value: git-tanstack[.]com
        source: https://tanstack.com/blog/postmortem-cve-2026-45321
        confidence: high
      - value: t.m-kosche[.]com
        source: https://www.endorlabs.com/blog/mini-shai-hulud-npm-worm-hits-sap-developer-packages
        confidence: high
    ips: []
    urls:
      - value: https://filev2.getsession[.]org/upload
        source: https://www.endorlabs.com/blog/mini-shai-hulud-npm-worm-hits-sap-developer-packages
        confidence: high
      - value: https://api.masscan[.]cloud/ping
        source: https://www.endorlabs.com/blog/mini-shai-hulud-npm-worm-hits-sap-developer-packages
        confidence: high
    hashes:
      - value: ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c
        source: https://www.endorlabs.com/blog/mini-shai-hulud-npm-worm-hits-sap-developer-packages
        confidence: high
    files:
      - value: router_init.js
        source: https://tanstack.com/blog/postmortem-cve-2026-45321
        confidence: high
      - value: setup_bun.js
        source: https://www.endorlabs.com/blog/mini-shai-hulud-npm-worm-hits-sap-developer-packages
        confidence: high
      - value: bun_environment.js
        source: https://www.endorlabs.com/blog/mini-shai-hulud-npm-worm-hits-sap-developer-packages
        confidence: high
      - value: transformers.pyz
        source: https://www.endorlabs.com/blog/mini-shai-hulud-npm-worm-hits-sap-developer-packages
        confidence: high
      - value: gh-token-monitor
        source: https://www.microsoft.com/en-us/security/blog/hunting-the-shai-hulud-supply-chain-worm
        confidence: high
    package_versions:
      - value: [email protected]
        source: https://www.sentinelone.com/blog/anatomy-of-cve-2026-45321
        confidence: high

    Detection and Hunting

    Script: local repository and exported telemetry scope

    #!/usr/bin/env python3
    import os
    import sys
    import json
    import subprocess
    from pathlib import Path
    
    ROOT = sys.argv[1] if len(sys.argv) > 1 else "."
    LOG_ROOT = os.environ.get("LOG_ROOT", "")
    OUT = Path(os.environ.get("OUT", "hp-mini-shai-hulud-worm-scope"))
    SINCE = "2026-04-20T00:00:00Z"
    UNTIL = "2026-05-23T23:59:59Z"
    
    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",
    ]
    VERSIONS = [
      "@tanstack/[email protected]",
      "@tanstack/[email protected]",
      "@tanstack/[email protected]",
      "@tanstack/[email protected]",
      "@tanstack/[email protected]",
      "@tanstack/[email protected]",
      "@tanstack/[email protected]",
      "@tanstack/[email protected]",
      "@antv/[email protected]",
      "@antv/[email protected]",
      "[email protected]",
      "@antv/* published 2026-05-19T01:39:00",
    ]
    FILES = [
      "router_init.js",
      "setup_bun.js",
      "bun_environment.js",
      "transformers.pyz",
      "gh-token-monitor",
    ]
    DOMAINS = [
      "filev2.getsession.org",
      "api.masscan.cloud",
      "git-tanstack.com",
      "t.m-kosche.com",
      "www.endorlabs.com",
      "www.microsoft.com",
      "www.sentinelone.com",
    ]
    URLS = [
      "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",
    ]
    IPS = [
    ]
    HASHES = [
      "ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c",
    ]
    PROCESS_PATTERNS = [
    ]
    NETWORK_PATTERNS = [
    ]
    
    # Positive signal: repository, lockfile, artifact, process, or network telemetry contains one of the exact incident selectors above.
    # Escalation: any match tied to a production build, CI run, deployed asset, or secret-bearing host moves the asset to presumed exposed.
    
    OUT.mkdir(parents=True, exist_ok=True)
    indicators_file = OUT / "indicators.txt"
    
    # Collect unique indicators
    indicators = set()
    for group in [PACKAGES, VERSIONS, FILES, DOMAINS, URLS, IPS, HASHES, PROCESS_PATTERNS, NETWORK_PATTERNS]:
        for val in group:
            if val:
                indicators.add(val)
    
    with open(indicators_file, "w") as f:
        for ind in sorted(indicators):
            f.write(ind + "\n")
    
    print(f"[+] Written unique selectors to {indicators_file}")
    
    # Walk local directory
    print(f"[+] Scanning directory: {ROOT} for selectors...")
    matches = []
    exclude_dirs = {"node_modules", "vendor", "dist", ".git"}
    for root, dirs, filenames in os.walk(ROOT):
        dirs[:] = [d for d in dirs if d not in exclude_dirs]
        for filename in filenames:
            filepath = Path(root) / filename
            try:
                content = filepath.read_text(errors="ignore")
                for ind in indicators:
                    if ind in content:
                        matches.append(f"{filepath}: found '{ind}'")
            except Exception:
                pass
    
    if matches:
        (OUT / "repository-indicator-matches.txt").write_text("\n".join(matches) + "\n")
        print(f"[!] Found {len(matches)} matches in codebase!")
    
    # Optional Log Scanning
    if LOG_ROOT and os.path.exists(LOG_ROOT):
        print(f"[+] Scanning telemetry log directory: {LOG_ROOT}...")
        log_matches = []
        for root, _, filenames in os.walk(LOG_ROOT):
            for filename in filenames:
                filepath = Path(root) / filename
                try:
                    content = filepath.read_text(errors="ignore")
                    for ind in indicators:
                        if ind in content:
                            log_matches.append(f"{filepath}: found '{ind}'")
                except Exception:
                    pass
        if log_matches:
            (OUT / "exported-telemetry-indicator-matches.txt").write_text("\n".join(log_matches) + "\n")
            print(f"[!] Found {len(log_matches)} matches in logs!")
    
        if PACKAGES:
            registry_dir = OUT / "registry"
            registry_dir.mkdir(exist_ok=True)
            for package in PACKAGES:
                if not package: continue
                safe_name = package.replace("/", "__")
                print(f"[+] Querying npm view for {package}...")
                res = subprocess.run(["npm", "view", package, "name", "version", "time", "versions", "dist-tags", "maintainers", "dist.tarball", "dist.integrity", "scripts", "--json"], capture_output=True, text=True)
                if res.returncode == 0:
                    (registry_dir / f"npm-{safe_name}.json").write_text(res.stdout)
            for package in PACKAGES:
                if not package: continue
                safe_name = package.replace("/", "__")
                print(f"[+] Querying pip index for {package}...")
                res = subprocess.run(["python3", "-m", "pip", "index", "versions", package], capture_output=True, text=True)
                if res.returncode == 0:
                    (registry_dir / f"pypi-{safe_name}-versions.txt").write_text(res.stdout)
                subprocess.run(["python3", "-m", "pip", "download", "--no-deps", package, "-d", str(registry_dir)], capture_output=True)
    
    print(f"[+] Wrote scope artifacts under {OUT}")

    Downstream Abuse Audits

    Script: GitHub organization run, release, secret, and workflow audit

    #!/usr/bin/env python3
    import os
    import sys
    import json
    import subprocess
    from pathlib import Path
    
    if "ORG" not in os.environ:
        print("ERROR: Set ORG environment variable to the GitHub organization to audit", file=sys.stderr)
        sys.exit(1)
    
    ORG = os.environ["ORG"]
    SINCE = "2026-04-20T00:00:00Z"
    UNTIL = "2026-05-23T23:59:59Z"
    OUT = Path(os.environ.get("OUT", "hp-mini-shai-hulud-worm-github-audit"))
    
    SELECTORS = [
      "@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",
      "@tanstack/[email protected]",
      "@tanstack/[email protected]",
      "@tanstack/[email protected]",
      "@tanstack/[email protected]",
      "@tanstack/[email protected]",
      "@tanstack/[email protected]",
      "@tanstack/[email protected]",
      "@tanstack/[email protected]",
      "@antv/[email protected]",
      "@antv/[email protected]",
      "[email protected]",
      "@antv/* published 2026-05-19T01:39:00",
      "router_init.js",
      "setup_bun.js",
      "bun_environment.js",
      "transformers.pyz",
      "gh-token-monitor",
      "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",
      "ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c",
    ]
    
    # Positive signal: a workflow run, release, secret, key, package, or workflow change overlaps the exposure window and references an incident selector.
    # Remediation trigger: unauthorized post-exposure write activity or a secret-bearing run matching an incident selector requires token revocation and downstream cloud/registry review.
    
    OUT.mkdir(parents=True, exist_ok=True)
    (OUT / "runs").mkdir(exist_ok=True)
    (OUT / "logs").mkdir(exist_ok=True)
    (OUT / "repos").mkdir(exist_ok=True)
    
    # 1. Write incident-selectors file
    selectors_file = OUT / "incident-selectors.txt"
    with open(selectors_file, "w") as sf:
        for s in SELECTORS:
            if s:
                sf.write(s + "\n")
    
    # 2. Get list of repos
    print(f"[+] Fetching repositories for organization: {ORG}")
    repo_res = subprocess.run(["gh", "repo", "list", ORG, "--limit", "1000", "--json", "nameWithOwner"], capture_output=True, text=True)
    if repo_res.returncode != 0:
        print(f"[-] Failed to fetch repos: {repo_res.stderr}", file=sys.stderr)
        sys.exit(1)
    
    repos = [r["nameWithOwner"] for r in json.loads(repo_res.stdout)]
    
    for repo in repos:
        safe_repo = repo.replace("/", "__")
        print(f"[+] Auditing repository: {repo}")
    
        # Check runs in the window
        runs_res = subprocess.run([
            "gh", "api", f"/repos/{repo}/actions/runs",
            "-f", "per_page=100",
            "-f", f"created=>={SINCE}",
            "--paginate"
        ], capture_output=True, text=True)
    
        if runs_res.returncode == 0:
            try:
                all_runs = json.loads(runs_res.stdout).get("workflow_runs", [])
                filtered_runs = [r for r in all_runs if r["created_at"] <= UNTIL]
    
                if filtered_runs:
                    with open(OUT / "runs" / f"{safe_repo}-runs.jsonl", "w") as rf:
                        for run in filtered_runs:
                            rf.write(json.dumps(run) + "\n")
    
                            # Fetch log dynamically
                            run_id = str(run["id"])
                            log_res = subprocess.run(["gh", "run", "view", run_id, "--repo", repo, "--log"], capture_output=True, text=True)
                            if log_res.returncode == 0:
                                (OUT / "logs" / f"{safe_repo}-{run_id}.log").write_text(log_res.stdout)
    
                            # Fetch details
                            view_res = subprocess.run(["gh", "run", "view", run_id, "--repo", repo, "--json", "databaseId,workflowName,headSha,event,createdAt,jobs"], capture_output=True, text=True)
                            if view_res.returncode == 0:
                                (OUT / "runs" / f"{safe_repo}-{run_id}.json").write_text(view_res.stdout)
            except Exception as e:
                print(f"[-] Error parsing runs for {repo}: {e}")
    
        # Check releases in window
        subprocess.run(["gh", "api", f"/repos/{repo}/releases", "-f", "per_page=100", "--paginate"], capture_output=True)
        # Check repo secrets updated in window
        subprocess.run(["gh", "api", f"/repos/{repo}/actions/secrets", "-f", "per_page=100", "--paginate"], capture_output=True)
        # Check deploy keys
        subprocess.run(["gh", "api", f"/repos/{repo}/keys", "-f", "per_page=100", "--paginate"], capture_output=True)
    
    # Scan output directory for any indicator selector matches
    print("[+] Scanning gathered telemetry for indicator matches...")
    subprocess.run(["rg", "-n", "--hidden", "--fixed-strings", "-f", str(selectors_file), str(OUT)], capture_output=False)
    
    print(f"[+] Wrote GitHub audit artifacts under {OUT}")

    Script: cloud OIDC and deployment credential follow-on audit

    #!/usr/bin/env python3
    import os
    import json
    import subprocess
    from pathlib import Path
    
    SINCE = "2026-04-20T00:00:00Z"
    UNTIL = "2026-05-23T23:59:59Z"
    OUT = Path(os.environ.get("OUT", "hp-mini-shai-hulud-worm-cloud-audit"))
    AWS_REGIONS = os.environ.get("AWS_REGIONS", "us-east-1").split(",")
    
    # Positive signal: token exchange or privileged write activity occurs in the exposure window from GitHub, CI/CD, package registry, or deployment automation identity.
    # Remediation trigger: unexpected write, deploy, IAM, secret, or registry activity tied to an exposed CI/CD path requires trust-policy disablement and credential rotation.
    
    OUT.mkdir(parents=True, exist_ok=True)
    
    # 1. AWS CloudTrail Audit
    print("[+] Querying AWS CloudTrail for Web Identity token exchanges...")
    aws_events = []
    for region in AWS_REGIONS:
        res = subprocess.run([
            "aws", "cloudtrail", "lookup-events",
            "--region", region,
            "--start-time", SINCE,
            "--end-time", UNTIL,
            "--lookup-attributes", "AttributeKey=EventName,AttributeValue=AssumeRoleWithWebIdentity",
            "--output", "json"
        ], capture_output=True, text=True)
    
        if res.returncode == 0:
            try:
                events = json.loads(res.stdout).get("Events", [])
                for event in events:
                    ct = json.loads(event.get("CloudTrailEvent", "{}"))
                    ct["region"] = region
                    aws_events.append(ct)
            except Exception as e:
                print(f"[-] Error parsing AWS CloudTrail events: {e}")
    
    if aws_events:
        with open(OUT / "aws-assume-role-with-web-identity.jsonl", "w") as f:
            for ev in aws_events:
                f.write(json.dumps(ev) + "\n")
    
        # Audit follow-on events for returned access keys
        for ev in aws_events:
            access_key = ev.get("responseElements", {}).get("credentials", {}).get("accessKeyId")
            region = ev.get("region", "us-east-1")
            if access_key:
                print(f"[+] Enumerating AWS events for AccessKey: {access_key}")
                f_res = subprocess.run([
                    "aws", "cloudtrail", "lookup-events",
                    "--region", region,
                    "--start-time", SINCE,
                    "--end-time", UNTIL,
                    "--lookup-attributes", f"AttributeKey=AccessKeyId,AttributeValue={access_key}",
                    "--output", "json"
                ], capture_output=True, text=True)
                if f_res.returncode == 0:
                    try:
                        f_events = json.loads(f_res.stdout).get("Events", [])
                        with open(OUT / "aws-follow-on-api-calls.jsonl", "a") as ff:
                            for fe in f_events:
                                ff.write(fe.get("CloudTrailEvent", "{}") + "\n")
                    except Exception as e:
                        print(f"[-] Error writing follow-on events: {e}")
    
    # 2. Azure Activity Log Audit
    print("[+] Querying Azure activity logs...")
    az_res = subprocess.run([
        "az", "monitor", "activity-log", "list",
        "--start-time", SINCE,
        "--end-time", UNTIL,
        "--query", "[?contains(operationName.value, 'write') || contains(operationName.value, 'delete') || contains(operationName.value, 'Microsoft.ManagedIdentity')]",
        "-o", "json"
    ], capture_output=True, text=True)
    
    if az_res.returncode == 0:
        (OUT / "azure-write-delete-activity.json").write_text(az_res.stdout)
    
    # 3. GCP Logging Audit
    print("[+] Querying GCP Cloud Logging...")
    gcp_filter = f'timestamp>="{SINCE}" AND timestamp<="{UNTIL}" AND (protoPayload.methodName="google.sts.v1.SecurityTokenService.ExchangeToken" OR protoPayload.methodName:"GenerateAccessToken" OR protoPayload.methodName:"CreateServiceAccountKey" OR protoPayload.methodName:"SetIamPolicy")'
    gcp_res = subprocess.run([
        "gcloud", "logging", "read", gcp_filter,
        "--format", "json"
    ], capture_output=True, text=True)
    
    if gcp_res.returncode == 0:
        (OUT / "gcp-token-and-iam-activity.json").write_text(gcp_res.stdout)
    
    print(f"[+] Wrote cloud audit artifacts under {OUT}")

    Script: registry metadata and artifact audit

    #!/usr/bin/env python3
    import os
    import json
    import subprocess
    from pathlib import Path
    
    SINCE = "2026-04-20T00:00:00Z"
    OUT = Path(os.environ.get("OUT", "hp-mini-shai-hulud-worm-registry-audit"))
    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",
    ]
    VERSIONS = [
      "@tanstack/[email protected]",
      "@tanstack/[email protected]",
      "@tanstack/[email protected]",
      "@tanstack/[email protected]",
      "@tanstack/[email protected]",
      "@tanstack/[email protected]",
      "@tanstack/[email protected]",
      "@tanstack/[email protected]",
      "@antv/[email protected]",
      "@antv/[email protected]",
      "[email protected]",
      "@antv/* published 2026-05-19T01:39:00",
    ]
    
    # Positive signal: registry metadata, package tarballs, or cached artifacts contain the exact affected package/version values.
    # Remediation trigger: any internal package cache, build artifact, or deployment using these package/version values requires exposure scoping.
    
    OUT.mkdir(parents=True, exist_ok=True)
    
    with open(OUT / "affected-versions.txt", "w") as av:
        for version in VERSIONS:
            if version:
                av.write(version + "\n")
    
        # 1. Audit npm dependencies in lockfiles/package.json
        print("[+] Scanning lockfiles for npm selectors...")
        for file in ["package-lock.json", "npm-shrinkwrap.json", "pnpm-lock.yaml", "yarn.lock", "package.json"]:
            if Path(file).exists():
                subprocess.run(["rg", "-n", "--hidden", "--fixed-strings", "-f", str(OUT / "affected-versions.txt"), file])
    
        # 2. Query registry metadata and fetch tarballs for local analysis
        metadata_dir = OUT / "metadata"
        tarballs_dir = OUT / "tarballs"
        metadata_dir.mkdir(exist_ok=True)
        tarballs_dir.mkdir(exist_ok=True)
        for package in PACKAGES:
            if not package: continue
            safe_name = package.replace("/", "__")
            print(f"[+] Querying npm view for {package}...")
            res = subprocess.run(["npm", "view", package, "time", "versions", "dist-tags", "maintainers", "dist.tarball", "dist.integrity", "scripts", "--json"], capture_output=True, text=True)
            if res.returncode == 0:
                (metadata_dir / f"npm-{safe_name}.json").write_text(res.stdout)
            subprocess.run(["npm", "pack", package, "--pack-destination", str(tarballs_dir)], capture_output=True)
    
        # 3. HOW TO REVOKE AND ROTATE EXPOSED NPM PUBLISHING TOKENS:
        # Revoke all compromised tokens via npm CLI:
        # subprocess.run(["npm", "token", "list"])
        # subprocess.run(["npm", "token", "revoke", "123456"])
        # Or logout to revoke the current session:
        # subprocess.run(["npm", "logout"])
        # Generate a new publishing token with MFA protection:
        # subprocess.run(["npm", "token", "create", "--read-only=false", "--cidr=0.0.0.0/0"])
    
    print(f"[+] Wrote registry audit artifacts under {OUT}")

    Campaign Update: @antv / atool npm Account Hijack — May 19, 2026

    On May 19, 2026, the Mini Shai-Hulud campaign added its most voluminous single wave: the compromise of the atool npm maintainer account, which held publishing rights over the entire @antv npm scope — Alibaba’s widely used open-source data visualization suite Orca Security. In a 27-minute window between 01:39 and 02:06 UTC, TeamPCP used the hijacked account to publish 639 malicious versions across 323 unique npm packages Cremit.io. The blast radius dwarfed the earlier TanStack wave in raw version count by nearly eight-to-one.

    Unlike the TanStack event — which exploited OIDC token theft via CI/CD cache poisoning — this wave exploited direct credential compromise of a single maintainer account (atool) that had been granted org-level publish rights across the entire @antv scope. The attacker did not need to touch GitHub Actions at all. Gaining control of one npm account with org-level permissions was sufficient to poison every downstream package simultaneously Palo Alto Networks Unit42.

    Attack Timeline

    • 2026-05-19T01:39:00Z — First malicious version published to npm under the @antv scope via the compromised atool account.
    • 2026-05-19T02:06:00Z — Final malicious version published; 639 versions across 323 packages in 27 minutes.

    Execution Mechanism

    Every poisoned package contained a preinstall lifecycle script set to "bun run index.js". The payload (index.js) was a single, heavily obfuscated 498 KB JavaScript file designed to execute under the Bun runtime rather than Node.js, thereby evading security tools that hook only the V8/Node process tree Chainguard. Any developer running npm install, yarn, or pnpm install for any of the 323 affected packages triggered payload execution immediately — before the package’s own code was loaded.

    Key packages confirmed in-scope include: @antv/g2, @antv/g6, @antv/x6, @antv/l7, @antv/s2, @antv/f2, echarts-for-react, timeago.js, size-sensor, and canvas-nest.js, plus 313 additional packages across the @antv namespace Orca Security.

    Credential Harvesting — 130+ File Paths

    The obfuscated payload conducted a sweep of the victim’s local filesystem targeting more than 130 specific file paths associated with cloud provider credentials, developer tokens, and SSH keys StepSecurity. Target categories included:

    Credential CategoryExample Paths Targeted
    AWS~/.aws/credentials, ~/.aws/config
    GCP~/.config/gcloud/application_default_credentials.json
    Azure~/.azure/accessTokens.json, ~/.azure/azureProfile.json
    Kubernetes~/.kube/config
    SSH keys~/.ssh/id_rsa, ~/.ssh/id_ed25519
    npm tokens~/.npmrc
    GitHub tokens~/.config/gh/hosts.yml, GITHUB_TOKEN env var

    Persistence

    Consistent with prior Mini Shai-Hulud waves, the payload established persistence via:

    • VS Code — appended a malicious run task to .vscode/tasks.json in the active workspace.
    • Claude Code — injected into ~/.claude/settings.json to intercept AI coding assistant commands.
    • OS daemons — created a gh-token-monitor LaunchAgent (macOS ~/Library/LaunchAgents/) or systemd unit (Linux) to harvest newly generated session tokens.

    Dual-Channel Exfiltration

    The May 19 wave introduced a more sophisticated exfiltration architecture than earlier waves Chainguard:

    Primary channel — GitHub API dead-drop: Harvested credentials were AES-encrypted and committed to newly created branches in the legitimate antvis/G2 GitHub repository via the GitHub API, using the victim’s own stolen GitHub token. This technique abuses a trusted public repository as a covert data sink, making the outbound traffic appear as normal GitHub API calls to monitoring tools StepSecurity.

    Fallback channel — HTTPS POST to t.m-kosche[.]com: If the dead-drop write failed (e.g., the GitHub token lacked repo write scope), the payload fell back to posting encrypted data to t.m-kosche[.]com disguised as OpenTelemetry trace data, using the Content-Type: application/x-protobuf header to blend with legitimate observability traffic Chainguard. This domain was already a known Mini Shai-Hulud IOC from prior waves; its appearance here confirms it as shared infrastructure across multiple TeamPCP operations.

    Worm Propagation

    With stolen npm tokens from victim environments, the worm searched for additional packages the victim had publish rights to and recursively poisoned them — the same self-propagating behavior documented in the TanStack wave Palo Alto Networks Unit42. The 323-package blast radius in 27 minutes reflects both the number of packages under the atool account’s control and automated worm propagation using tokens harvested from systems compromised in earlier waves.

    Attribution

    Palo Alto Networks Unit42 attributes this wave explicitly to TeamPCP, correlating the t.m-kosche[.]com C2 infrastructure, the Bun runtime smuggling technique, the GitHub API dead-drop pattern, and the 130+ credential file path list with prior documented Mini Shai-Hulud infrastructure Palo Alto Networks Unit42.

    New IOCs — May 19, 2026 Wave

    dead_drop:
      - repository: antvis/G2
        mechanism: "GitHub API branch writes using victim's stolen GitHub token"
        confidence: high
        source: https://www.stepsecurity.io/blog/atool-antv-npm-supply-chain-attack
    domains:
      - value: t.m-kosche.com
        role: "fallback C2; HTTPS POST disguised as OpenTelemetry trace data"
        confidence: high
        source: https://www.chainguard.dev/unchained/atool-antv-npm-supply-chain
    files:
      - value: index.js
        description: "498 KB obfuscated JavaScript payload; executed via 'bun run index.js' preinstall hook"
        confidence: high
        source: https://orca.security/resources/blog/atool-antv-npm-supply-chain-attack/
    package_versions:
      - value: "@antv/* (639 versions across 323 packages)"
        published_window: "2026-05-19T01:39:00Z / 2026-05-19T02:06:00Z"
        confidence: high
        source: https://cremit.io/blog/antv-npm-supply-chain/

    Sources

    1. 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.
    2. SentinelOne. Role: PRIMARY_RESEARCH Impact: Deep technical walkthrough of the cache-poisoning exploit, the pull_request_target misconfiguration, and downstream Nx Console compromise.
    3. Endor Labs. Role: PRIMARY_RESEARCH Impact: Initial discovery of the worm’s SAP CAP targets, Bun runtime smuggling, and Python transformers.pyz payload.
    4. Wiz.io. Role: PRIMARY_RESEARCH Impact: Analysis of the Sigstore SLSA Build Level 3 provenance forgery and the federation bypass.
    5. Tenable. Role: SECONDARY_ANALYSIS Impact: Discussion of TeamPCP’s Dune-themed indicators, exfiltration repositories, and worm-like lateral movement.
    6. 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.
    7. Aikido Security. Role: SECONDARY_ANALYSIS Impact: Overall synthesis of the massive May 2026 supply chain wave and remediation recommendations.
    8. 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.
    9. Palo Alto Networks Unit42. Role: PRIMARY_RESEARCH Impact: Attribution to TeamPCP Mini Shai-Hulud campaign, correlation with prior waves, worm propagation analysis.
    10. 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.
    11. StepSecurity. Role: DIRECT_SOURCE Impact: Dead-drop commit analysis in antvis/G2 repo, 130+ credential file paths targeted, GitHub API exfiltration mechanism.
    12. Cremit.io. Role: SECONDARY_ANALYSIS Impact: 01:39–02:06 UTC attack window timing, 639 malicious versions across 323 packages, package list.
    13. 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.
    14. 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/[email protected] as a related payload variant.

    IOC Clipboard

    19 IOCs
    Defang IOCs
    domain filev2.getsession.org filev2[.]getsession[.]org
    domain api.masscan.cloud api[.]masscan[.]cloud
    domain git-tanstack.com git-tanstack[.]com
    domain t.m-kosche.com t[.]m-kosche[.]com
    domain www.endorlabs.com www[.]endorlabs[.]com
    domain www.microsoft.com www[.]microsoft[.]com
    domain www.sentinelone.com www[.]sentinelone[.]com
    url https://filev2.getsession.org/upload hxxps://filev2[.]getsession[.]org/upload
    url https://api.masscan.cloud/ping hxxps://api[.]masscan[.]cloud/ping
    url 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
    url https://tanstack.com/blog/postmortem-cve-2026-45321 hxxps://tanstack[.]com/blog/postmortem-cve-2026-45321
    url 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
    url https://www.sentinelone.com/blog/anatomy-of-cve-2026-45321 hxxps://www[.]sentinelone[.]com/blog/anatomy-of-cve-2026-45321
    hash ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c
    file router_init.js router_init.js
    file setup_bun.js setup_bun.js
    file bun_environment.js bun_environment.js
    file transformers.pyz transformers.pyz
    file gh-token-monitor gh-token-monitor