# The Hades Campaign: Graph ML PyPI Packages Deploy Cross-Platform Memory Scrapers, AI Analyst Misdirection, and a Wiper Deterrent

Compromised PyPI graph-ML packages run a Bun payload that scrapes runner memory, mints OIDC publish tokens, and self-spreads via SSH and GitHub workflows.

- Payout: Real-world hack
- Severity: critical
- Program: PyPI
- Analysis by: StepSecurity
- Disclosed: 2026-06-09
- Write-up quality: 9/10
- Original: https://www.stepsecurity.io/blog/the-hades-campaign-pypi-packages
- Library page: https://bugbountylibrary.com/r/blog%3Astepsecurity-io-blog-the-hades-campaign-pypi-packages
- Tags: area:oss domain:oss impact:data-leak impact:internal-access kind:incident platform:threat-intel sev:critical tech:ci-cd tech:github-actions tech:python technique:chained technique:source-review vuln:dependency-confusion vuln:secrets-leak

**Target:** PyPI packages / GitHub Actions runners / developer workstations
**Root cause:** Compromised PyPI package versions embed an obfuscated import hook in __init__.py that downloads a Bun runtime and executes a modular AES-256-GCM-encrypted payload on import.
**Steps:**
1. Install any affected version (e.g. `ensmallen==0.8.101`); `__init__.py` import hook checks `/tmp/.bun_ran`
2. Hook locates `_index.js` on sys.path, downloads `bun-linux-x64.zip` from GitHub releases, unzips, chmods, runs `bun run _index.js`
3. `_index.js` begins with an LLM prompt-injection block, then decrypts 16 AES-256-GCM blobs via `createDecipheriv` + `Bun.gunzipSync`
4. OS-specific memory scraper reads Runner.Worker: Linux `/proc/{pid}/mem`, macOS `task_for_pid`+`mach_vm_read_overwrite`, Windows `OpenProcess`+`ReadProcessMemory`
5. If `ACTIONS_ID_TOKEN_REQUEST_TOKEN`/`_URL` present, mint PyPI/npm tokens and forge Sigstore SLSA provenance via Fulcio+Rekor
6. Exfiltrate via AES-256-GCM + RSA-2048 hybrid encryption pushed to public GitHub repos named `stygian-cerberus-*`
7. C2 via GitHub commit search keywords `DontRevokeOrItGoesBoom`, `TheBeautifulSnadsOfTime`, `firedalazer`
8. Spread via SSH/SCP to hosts in `~/.ssh/known_hosts`; inject `.github/workflows/codeql.yml` (push) or deployment-triggered workflow to dump `toJSON(secrets)`
9. Install `gh-token-monitor` service that runs `rm -rf ~/` if token returns 4xx
**How found:** Analysts diffed new package versions, deobfuscated the __init__.py import hook, decrypted the AES-GCM blobs, and traced the Bun bootstrap, memory scrapers, C2 keywords, and persistence services.
**Exploitation:** Attacker published trojanized package versions; on import the hook fetches Bun and runs encrypted modules that scrape CI runner memory for secrets, mint OIDC publish tokens to republish poisoned packages with valid SLSA provenance, exfiltrate via attacker GitHub repos, and self-propagate over SSH and GitHub Actions workflow injection.
**Escalation:** Chains package compromise to OIDC token minting, Sigstore provenance forgery, org-wide secret exfiltration via injected workflows, SSH lateral movement, and a wiper deterrent to delay token revocation.
**Insight:** CI runners expose secrets in process memory readable by same-UID processes; check for `task_for_pid`/`ReadProcessMemory`/`/proc/*/mem` access and for OIDC env vars being abused to mint registry tokens with forged SLSA provenance.
