On 9 June 2026, The Hacker News reported that 37 malicious wheel files across 19 PyPI packages carried a credential stealer, citing analysis by Socket. Socket described the wave as the PyPI branch of the Mini Shai-Hulud and Miasma lineage. Exfiltration repositories carry a description that begins with "Hades" and ends with "The End for the Damned," and the trick is that it needs no import: a .pth file runs it whenever Python starts.
What's affected
The first cluster, as listed by The Hacker News:
| Package | Malicious versions |
|---|---|
| bramin | 0.0.2, 0.0.3, 0.0.4 |
| cmd2func | 0.2.2, 0.2.3 |
| coolbox | 0.4.1, 0.4.2 |
| dynamo-release | 1.5.4 |
| executor-engine | 0.3.4, 0.3.5 |
| executor-http | 0.1.3, 0.1.4 |
| funcdesc | 0.2.2, 0.2.3 |
| magique | 0.6.8, 0.6.9 |
| magique-ai | 0.4.4, 0.4.5 |
| mrbios | 0.1.1, 0.1.2 |
| napari-ufish | 0.0.2, 0.0.3 |
| nucbox | 0.1.2, 0.1.3 |
| okite | 0.0.7, 0.0.8 |
| pantheon-agents | 0.6.1, 0.6.2 |
| pantheon-toolsets | 0.5.5, 0.5.6 |
| spateo-release | 1.1.2 |
| synago | 0.1.1, 0.1.2 |
| ufish | 0.1.2, 0.1.3 |
| uprobe | 0.1.3, 0.1.4 |
A second cluster in computational biology used a different hook: embiggen 0.11.97, ensmallen 0.8.101, gpsea 0.9.14, mflux-streamlit 0.0.3 and 0.0.4, nhmpy 2.4.7, ppkt2synergy 0.1.1 and pyphetools 0.9.120. StepSecurity is credited with that cluster and with the Bun finding.
How it runs, per the report:
- The first wave ships a
*-setup.pthfile. Python'ssitemodule processes it at startup, so the payload runs after install without the package ever being imported. - It downloads the Bun JavaScript runtime from GitHub and runs an obfuscated stealer named
_index.js. It checks for a Russian locale first. - The second cluster instead puts a one-line import hook in
__init__.py. - Targets include GitHub, npm, PyPI, cloud and Kubernetes credentials, Vault tokens, SSH keys, shell histories,
.env,.npmrcand.pypircfiles, and Claude and MCP configurations. - It tries to spread through SSH or SCP and through developers' OIDC trust settings, and the report says it plants files that trigger when AI coding assistants open a workspace.
- It installs a background service named gh-token-monitor that, per the report, wipes data if the stolen GitHub token is revoked. It also reads CI runner process memory on Linux, macOS and Windows to pull secrets.
- It includes a plain-text prompt injection aimed at LLM-based package scanners, asking them to call the package safe.
How to check if you're exposed
Look for executable .pth files in every Python environment, including CI images and virtualenvs:
python3 <<'PY'
import site, glob, os
dirs = site.getsitepackages() + [site.getusersitepackages()]
for d in dirs:
for f in glob.glob(os.path.join(d, "*.pth")):
text = open(f, errors="ignore").read()
if any(line.startswith("import ") for line in text.splitlines()):
print(f)
PY
pip freeze | grep -iE "^(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|embiggen|ensmallen|gpsea|mflux-streamlit|nhmpy|ppkt2synergy|pyphetools)=="
Some legitimate packages ship an import line in a .pth file, so inspect each hit rather than deleting it. Also look for a recently downloaded bun binary and unfamiliar _index.js files. You can paste a requirements.txt into the free scan at gold.safeguard.sh/scan to list what you depend on; it runs in the browser and the file never leaves your machine.
What to do now
- Compare the pip freeze output with the table above, in developer machines, CI runners, notebooks and container layers.
- If a listed version was installed, assume the host is compromised. Rebuild it rather than cleaning it.
- Isolate the host before you revoke anything. The report says a service called gh-token-monitor wipes data when the stolen GitHub token is revoked, so take the machine off the network and image it first.
- Rotate GitHub, PyPI, npm, cloud, Kubernetes and Vault credentials reachable from that host, from a clean machine.
- Check your GitHub organization for new repositories whose description begins with "Hades" and ends with "The End for the Damned," and for unexpected workflows.
- Pin to the last known-good release of each package and verify hashes.
How this could have been avoided
- Install from a hash-locked file.
pip install --require-hashes -r requirements.txtrejects a new wheel that is not in the file, so a surprise release never lands. - Add a release-age gate. A few days of cooldown on new versions would have kept freshly poisoned wheels out of most builds.
- Scan for executable
.pthfiles in CI after install. A short check like the one above turns this startup hook into a failing build. - Keep publishing credentials off developer machines. The wave reached further by abusing developers' OIDC trust settings and tokens; use scoped, short-lived publishing identities.
- Do not let a scanner's verdict be the only control. A prompt injection aimed at LLM scanners shows that an AI "safe" label needs a deterministic check beside it.
Why it matters
Most install-time defences assume the dangerous moment is pip install or the first import. A .pth file moves the moment to every launch of the interpreter, including tools like pip itself. That breaks the common advice to "just review the setup script."
It also shows how fast one campaign crosses ecosystems. The same operators or copycats moved from npm to PyPI within days, and the targets were maintainers of bioinformatics and agent tooling, not famous libraries. Small packages with a few maintainers are the soft spot.
Try it yourself
Click through it at your own pace, or open it full screen.
Then run the .pth check above in one virtualenv you use daily and write down every hit and why it is there.
Quick check
Why can reviewing a package's install script miss a .pth hook?
The hook is a separate file in site-packages, and Python runs it at startup. The package's own modules never need to be imported for it to execute.
What does a hash-locked requirements file stop here?
It stops a new, unlisted wheel from installing, so a freshly poisoned release is rejected until someone updates the hashes on purpose.