Safeguard
Supply Chain Security•

Hades: PyPI packages that run a stealer on every Python start

In June 2026 Socket traced 19 PyPI packages and 37 wheels to a Miasma branch called Hades. A .pth file runs the payload at interpreter startup, before any import.

Safeguard Security Team
6 min read

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:

PackageMalicious versions
bramin0.0.2, 0.0.3, 0.0.4
cmd2func0.2.2, 0.2.3
coolbox0.4.1, 0.4.2
dynamo-release1.5.4
executor-engine0.3.4, 0.3.5
executor-http0.1.3, 0.1.4
funcdesc0.2.2, 0.2.3
magique0.6.8, 0.6.9
magique-ai0.4.4, 0.4.5
mrbios0.1.1, 0.1.2
napari-ufish0.0.2, 0.0.3
nucbox0.1.2, 0.1.3
okite0.0.7, 0.0.8
pantheon-agents0.6.1, 0.6.2
pantheon-toolsets0.5.5, 0.5.6
spateo-release1.1.2
synago0.1.1, 0.1.2
ufish0.1.2, 0.1.3
uprobe0.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.pth file. Python's site module 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, .npmrc and .pypirc files, 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

  1. Compare the pip freeze output with the table above, in developer machines, CI runners, notebooks and container layers.
  2. If a listed version was installed, assume the host is compromised. Rebuild it rather than cleaning it.
  3. 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.
  4. Rotate GitHub, PyPI, npm, cloud, Kubernetes and Vault credentials reachable from that host, from a clean machine.
  5. Check your GitHub organization for new repositories whose description begins with "Hades" and ends with "The End for the Damned," and for unexpected workflows.
  6. 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.txt rejects 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 .pth files 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.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.