Back to all posts
Critical Supply Chain

Hades Campaign:
A PyPI Worm Hunting AI and Bioinformatics Developers

A self-spreading worm planted a Python startup hook in dozens of PyPI projects, then used the Bun runtime to run an obfuscated stealer that harvests GitHub, cloud, and registry credentials from developer machines and CI runners. CyberXYZ flagged the wave before it was public.

CyberXYZ Security Team Threat Intelligence
11 min read
If you installed any affected package, treat the machine as compromised

The Hades payload runs the moment a poisoned package is imported, not just at install time, and it targets CI runners as well as workstations. If a build agent or developer laptop pulled one of the versions listed below, rotate every credential that machine could reach (GitHub, npm, PyPI, cloud, SSH) and audit recent GitHub Actions runs and new public repositories under your accounts.

What happened

A threat actor that researchers track as Miasma published a fresh wave of malicious packages to PyPI in early June 2026. The newest branch of the operation, named Hades after the underworld theme woven through its infrastructure, poisoned roughly 37 PyPI projects across about 60 artifacts. It is part of the broader Mini Shai-Hulud lineage, a family of self-propagating supply-chain worms that steal developer and CI secrets and use them to publish the next generation of infected packages.

What makes Hades worth a close read is the delivery mechanism. Instead of a classic setup.py install hook, the operators shipped a Python path-configuration file, a .pth file, alongside an obfuscated JavaScript payload named _index.js. Any line in a .pth file that begins with import is executed by the Python interpreter every single time the environment starts. That turns a quiet one-line file into reliable code execution that fires on import rather than on install, and it survives in the site-packages directory long after the package looks installed and idle.

The campaign was aimed with intent. Two clusters stand out: bioinformatics tooling used for graph learning, patient phenotyping, and phenopacket workflows, and a set of packages dressed up as Model Context Protocol and AI developer tools. The names were chosen to look at home in the import lists of research labs and AI engineering teams, two communities that move fast and pull niche packages without much friction.

Why traditional scanners struggled

The loader opens with a block of fake system instructions written as a JavaScript comment, a deliberate prompt injection meant to convince automated AI reviewers that the package is clean infrastructure. The real logic sits behind a try{eval(...)} wrapper, a character-code array, and a ROT-style substitution routine, with sixteen functional payloads each encrypted under its own AES-256-GCM key and gzip-compressed. A scanner that reads source strings sees almost nothing, and an LLM reviewer that ingests the raw file can be talked out of its own verdict. CyberXYZ never hands a raw payload to a model and asks it to decide. We ship structured evidence (the install-hook behavior, the cross-package correlation, the sandbox trace) to the model and force a constrained verdict, so a comment header cannot rewrite the conclusion.

How CyberXYZ caught the wave at hour zero

Our PyPI firehose ingested the malicious releases as they were published, on June 5 and June 6, 2026. One of the projects in the cluster had been on our radar since late May. That is days before the first public write-ups appeared on June 7 and June 8.

Three detection layers fired in sequence. First, the tarball scanner flagged the Python startup hook on every affected wheel under the pth_executable_import signal, and surfaced the credential-dumping behavior on several of them under a separate environment-harvest signal. Second, the cross-signal scorer fused those behaviors into block and alert decisions for the individual packages. Third, and most important for a worm, the wave correlator recognized the cluster as a single coordinated event.

The wave correlator does something per-package scanners cannot. It fingerprints the actual install-hook payload, not the package name or the install command, and groups packages that ship the same malicious code within a tight publishing window. Because every Hades wheel carried an identical startup hook, the correlator collapsed the whole set into one confirmed wave rather than dozens of disconnected alerts. That is the difference between a triage queue full of noise and a single line that reads: a coordinated multi-package campaign is publishing right now.

The signal that matters for worms

Mass-token theft worms win by volume and speed. A defender who reviews each package in isolation will catch the obvious ones and miss the marginal members. Clustering on the shared payload turns the volume against the attacker: the more packages they push with the same hook, the louder the wave reads.

Timeline of events

The attack chain in detail

The execution flow is compact and resilient, built so a single import triggers the whole chain and so it only runs once per host.

  1. Startup hook fires on import. The poisoned wheel ships a *-setup.pth file. Its single executable line checks for a sentinel at /tmp/.bun_ran. If the sentinel is absent, deobfuscated Python runs.
  2. Bun is fetched on demand. The hook scans sys.path for the bundled _index.js, downloads the Bun JavaScript runtime from the official Bun GitHub releases if it is not already present, and writes the sentinel so the host is not infected twice.
  3. The JavaScript stealer runs. The hook executes bun run _index.js. The bundle decrypts its sixteen payloads at runtime and selects an operating-system-specific module.
  4. Memory and secrets are scraped. On Linux the stealer walks process memory through /proc, on macOS it calls Mach kernel VM APIs through ctypes without needing root, and on Windows it compiles C# on the fly through PowerShell to read process memory. The high-value target is the CI runner worker process, where it lifts plaintext environment variables and the workflow GITHUB_TOKEN.
  5. Exfiltration hides inside GitHub. Harvested secrets are gzip-compressed, encrypted with a fresh AES-256-GCM key, and the key itself is wrapped with the attacker's RSA public key. The bundle is pushed to a newly created public repository under the victim's own GitHub account. Some samples also route data to a destination styled to look like a legitimate Anthropic API endpoint, a camouflage trick that lets exfiltration blend into normal AI-tooling traffic.

Hades does not stop at theft. It establishes long-running persistence through user-level service files on Linux and Launch Agents on macOS, drops workspace backdoors into editor and AI-assistant configuration files (for example .cursorrules, .windsurfrules, .github/copilot-instructions.md, and .claude/settings.json) so the payload re-arms inside developer tooling, and injects a GitHub Actions workflow disguised under a benign name to dump repository secrets through the standard artifact upload path. The operators also built a deterrent into the design, wiring destructive behavior to trigger if the stolen token is revoked, which pressures victims to leave the access live.

Two more details show how deliberate this campaign is. First, the loader is not the only delivery path: some samples ship native compiled extensions (.abi3.so files) that execute at import time, so blocking .pth files alone does not close the door. Second, the malware actively hunts for defensive tooling on the host, checking for runtime-security agents and CI hardening tools and their endpoints, and it probes the Docker socket at /var/run/docker.sock for container escape and lateral movement. This is a worm that expects to land on a defended CI runner and tries to work around the defenses it finds.

Indicators of compromise

The following indicators are drawn from published analysis of the Hades branch. File hashes are per-artifact, so treat the names and behaviors as the durable signal and the hashes as confirmation for the specific wheels listed.

TypeIndicatorContext
Payload file _index.js Obfuscated Bun-executed JavaScript stealer bundled in the wheel
Startup hook *-setup.pth Python path-configuration file that executes on every interpreter start
Sentinel file /tmp/.bun_ran Run-once marker written after first execution
Propagation file /tmp/.sshu-setup.js Staging artifact used during self-spread
SHA-256 6506d31707a39949f89534bf9705bcf889f1ecae3dbc6f4ff88d67a8be3d01b2 langchain_core-setup.pth
SHA-256 6d332f814f15f19758d65026bbfd0a8c49671b319ec77b8fa1b27fc48afff7d9 langchain_core_mcp-1.4.2-py3-none-any.whl
Exfil endpoint api.anthropic.com/v1/api Look-alike path used to disguise data exfiltration as AI-tool traffic
Runtime download github.com/oven-sh/bun/releases/download/bun-v1.3.13 Bun runtime pulled on demand to execute the payload (v1.3.13 to v1.3.14 observed)
Upload marker Bun/1.3.14 User-agent observed on malicious PyPI uploads
C2 commit marker DontRevokeOrItGoesBoom Keyword for the encrypted token dead-drop channel
C2 commit marker TheBeautifulSnadsOfTime Keyword for the signed JavaScript command channel
C2 commit marker firedalazer Keyword for the Python dropper polling channel
Exfil repo pattern stygian-cerberus-[0-9]+, tartarean-charon-[0-9]+ Attacker-created public repositories used as encrypted dead-drops
Repo description Hades - The End for the Damned Description string set on exfiltration repositories
Injected workflow .github/workflows/codeql.yml (name "Run Copilot") Disguised workflow that dumps secrets to the format-results artifact
Persistence (Linux) ~/.config/systemd/user/update-monitor.service User-level C2 polling service
Persistence (macOS) ~/Library/LaunchAgents/com.user.update-monitor.plist Launch Agent C2 service

Affected PyPI projects

Across the reported waves the affected names include, among others: bramin, cmd2func, coolbox, dynamo-release, executor-engine, executor-http, funcdesc, magique, magique-ai, mrbios, napari-ufish, nucbox, okite, pantheon-agents, pantheon-toolsets, spateo-release, synago, ufish, uprobe, ensmallen, embiggen, gpsea, pyphetools, ppkt2synergy, phenopacket-store-toolkit, nhmpy, mflux-streamlit, rlask, tlask, rsquests, dreamgen, mem8, orchestr8-platform, ray-mcp-server, langchain-core-mcp, instructor-mcp, openai-mcp, and tiktoken-mcp. Confirm exact compromised versions against the official PyPI advisories before remediation.

How to check your environment

Search your site-packages directories for any *-setup.pth file that contains an import statement, and for a bundled _index.js next to an installed package. Look for the sentinel /tmp/.bun_ran and for an unexpected Bun binary in a temp path. On CI, review recent GitHub Actions runs for short-lived workflows named like Run Copilot or files under .github/workflows/codeql*.yml that were added and quickly deleted, and check for an artifact named format-results. Audit your GitHub account for new public repositories you did not create, especially with underworld-themed names or the description string above.

What was compromised

The stealer casts a wide net across developer and CI secrets: GitHub personal access tokens and the workflow GITHUB_TOKEN, npm, PyPI, RubyGems, and JFrog registry credentials, cloud provider keys, Kubernetes service-account material, SSH private keys, Docker configuration, shell histories, and the contents of .env files and AI developer-tool configurations. Because the worm uses stolen registry credentials to publish the next round of poisoned packages, every successful theft is also fuel for the campaign's spread.

Supply-chain risk analysis

Hades is a clean example of why publish-time behavior matters more than package reputation. None of these projects were typosquats of famous libraries in the traditional sense. They were real-looking research and AI tools whose only tell was the malicious code they suddenly started shipping. The relevant techniques map cleanly to the MITRE ATT&CK framework.

TechniqueIDUse in Hades
Compromise Software Supply Chain T1195.001 Malicious PyPI packages delivered to developers and CI
Command and Scripting Interpreter, JavaScript T1059.007 Payload executed through the Bun runtime
Steal Application Access Token T1528 GITHUB_TOKEN and registry credential theft
Credentials from Process Memory T1003.007 Reading the CI runner worker process memory
Unsecured Credentials in Files T1552.001 Harvesting SSH keys, .env, and registry config files
Application Layer Protocol, Web T1071.001 GitHub API and commit markers used as a C2 channel
Exfiltration to Code Repository T1567.001 Encrypted secrets pushed to attacker-created GitHub repos
Event Triggered Execution, Startup T1546 Python .pth startup hook firing on every interpreter launch

Recommended actions

Immediate. Remove any affected package version from your environments and registries. On any host that imported one, rotate GitHub tokens, registry tokens, cloud keys, and SSH keys, and revoke the workflow tokens for affected repositories. Review and remove any unexpected public repositories, GitHub Actions workflows, persistence services, and editor or AI-assistant config backdoors described above. Assume secrets reachable from the host are exposed.

Short term. Pin dependencies and require review for version bumps on the packages your research and AI teams pull. Treat install-time and import-time code execution as a first-class risk in review, not a footnote. Constrain CI runner permissions so a single poisoned import cannot reach every secret in the environment, and prefer short-lived, least-privilege tokens.

Long term. Put a publish-time behavioral gate in front of your ecosystem. Reputation and version age do not catch a trusted project that suddenly ships a startup hook. Correlation across packages does. A worm that pushes the same payload into thirty projects should read as one loud event, not thirty quiet ones.

How CyberXYZ helps

CyberXYZ watches npm, PyPI, and other registries at publish time, scores each release across behavioral signals, and correlates coordinated waves so a worm surfaces as a single campaign at hour zero. For the Hades wave, that meant the cluster was flagged and grouped before the public advisories landed. Talk to our team to put the same gate in front of your supply chain.


References

  1. StepSecurity, The Hades Campaign: Malicious PyPI Packages
  2. Socket, Mini Shai-Hulud, Miasma, and Hades Worms Target Bioinformatics and MCP Developers
  3. MITRE ATT&CK, T1195.001 Compromise Software Supply Chain
  4. CyberXYZ Threat Intelligence, internal detection telemetry, June 2026
馃憢

Let's Talk

Want to learn how CyberXYZ protects your supply chain? We'd love to hear from you.