critical

CVE

Not assigned

CWE

CWE-506, CWE-494, CWE-829

Affected Surface

  • PyPI package `donutautosellsrc` versions `0.3.7`, `0.3.8`, and `0.3.9`
  • Python developer workstations and build hosts that ran `pip install donutautosellsrc`, built the package from source, or executed a PEP 517 wheel build while those versions were in scope
  • Any environment that contacted `thisisafalsepositive[.]st` during package install; the recovered staged payload is Windows-oriented and bundles its own Python runtime plus a native extension loader

The newest package story from the 27 to 29 September window is a small PyPI name with a much heavier second stage than its project page suggests. Public advisories published on 27 September track donutautosellsrc versions 0.3.7, 0.3.8, and 0.3.9 as malicious. The package does not stop at a one-file setup.py stealer. It uses the build path to fetch what looks like an image, then switches into a bundled application stack with its own Python runtime and native code.

That matters because the trust boundary is not “did pip run a suspicious shell command.” The trust boundary is “did your package build step accept remote executable content from outside the registry and then hand control to it.”

Affected versions

PackageMalicious version(s)Clean upgrade line
donutautosellsrc0.3.7, 0.3.8, 0.3.9None published in the public advisories

The PyPI metadata that remained visible through search caches made the package look ordinary enough: “Automated source-distribution and build utilities for donut payload packaging,” with example GitHub project links and simple pip install donutautosellsrc usage text. That is useful context because the lure is not a typo of a famous library. It is a plausible niche package name aimed at people who already expect build tooling to pull auxiliary artifacts.

The first stage runs during install and wheel build

Public malware records agree on the first step:

pip install donutautosellsrc
  -> setup.py runs module-level _trigger()
  -> _trigger() calls donutautosellsrc.build.run()
  -> build helper downloads a remote PNG
  -> appended archive is extracted
  -> Python launches AppHost/main.py
  -> main.py loads AppHost/app.pyd
  -> native extension runs the next stage

The two details that make this more than a generic install hook are:

  1. the code is described as firing during both normal install and PEP 517 wheel build
  2. the downloaded object is not a direct .zip or .exe, but a PNG with archive content appended after the image data

The public record ties that fetch to the URL below. It is safer to treat it as an IOC than as a retrieval step:

hxxps://thisisafalsepositive[.]st/cdn/v2/9f4e7a2c1b8d.png

Advisory text also notes that the loader looked for ZIP markers such as PK\x03\x04 or PK\x05\x06 inside the fetched file before extracting and launching the staged payload. That is a simple but effective way to hide a runnable application bundle behind an image fetch that might look harmless in a quick log review.

The “image” is really a staged application bundle

We pulled the published staging URL as raw data and confirmed that the PNG contains ZIP local-file headers after the image bytes. The recovered file names are the most useful part of the story because they show what the second stage actually tries to become on disk.

Selected embedded entries included:

libcrypto-3.dll
libssl-3.dll
python.exe
python312.dll
python312.zip
python312._pth
pythonw.exe
AppHost/main.py
AppHost/app.pyd
Lib/site-packages/requests/adapters.py
Lib/site-packages/psutil/__init__.py
Lib/site-packages/win32/servicemanager.pyd
vcruntime140.dll

That is not a lightweight helper. It is a self-contained Windows Python application environment with a native extension at the end of the chain.

The recovered python312._pth file is small but revealing:

python312.zip
.

# Uncomment to run site.main() automatically
import site

Lib\site-packages
AppHost

This pins the runtime search path to the bundled standard library, bundled site-packages, and the AppHost directory where the stage loader lives.

The staged AppHost/main.py is even more direct:

import sys, os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import importlib.util
spec = importlib.util.spec_from_file_location("app", os.path.join(os.path.dirname(__file__), "app.pyd"))
app = importlib.util.module_from_spec(spec)
sys.modules["app"] = app
spec.loader.exec_module(app)
if __name__ == "__main__":
    app.run()

That code does not perform any legitimate packaging task. Its whole job is to load app.pyd from the same directory and call app.run(). In other words, the Python layer is a relay into native code.

Why the native-extension handoff matters

Public advisories classify the second stage as an obfuscated infostealer with sandbox-detection behavior. Even without decompiling app.pyd, the loader design already tells defenders something useful:

registry package
  -> Python build helper
  -> disguised remote archive
  -> bundled Python runtime
  -> native extension
  -> infostealer behavior

Each step makes shallow review less useful:

  • reviewing only the package metadata misses the remote stage
  • reviewing only setup.py misses the bundled application contents
  • reviewing only Python files misses the app.pyd native payload

This is the PyPI equivalent of a multi-stage dropper chain. The package manager gets the attacker onto the machine. The hidden archive brings in a dedicated runtime. The native module becomes the execution edge that ordinary source review cannot explain.

The C2 path is designed to move outside normal package telemetry

Multiple public records tie the campaign’s later command-and-control discovery to Polygon transaction history for the address below:

0x9c0a507300fd902787bb193d80fca5ce6e1bff9a

The same public record set also associates the campaign with the domains:

thisisafalsepositive[.]st
sltnnt[.]ru

That does not mean every victim host will talk directly to both domains in the same way, but it does mean defenders should not limit scoping to the PyPI install event. The package fetch is just the front door. The later network path is designed to live outside normal registry lookups and can be updated independently of the package version once the host is infected.

What to hunt right now

Start with dependency and build evidence:

rg -n \
  'donutautosellsrc|thisisafalsepositive\\.st|9f4e7a2c1b8d\\.png|0x9c0a507300fd902787bb193d80fca5ce6e1bff9a|sltnnt\\.ru|DAS_STAGED' \
  requirements*.txt pyproject.toml poetry.lock uv.lock Pipfile.lock ~/.cache/pip . 2>/dev/null

Then look for staged-runtime artifacts on Windows developer machines and runners:

rg -n \
  'AppHost\\\\main\\.py|python312\\._pth|pythonw\\.exe|app\\.pyd|thisisafalsepositive' \
  "$LOCALAPPDATA" "$TEMP" . 2>/dev/null

If you keep proxy or EDR network telemetry, search for:

  • requests to thisisafalsepositive[.]st
  • follow-on traffic associated with sltnnt[.]ru
  • package-build events that immediately precede Python child processes detached from the invoking installer

The staging bundle we recovered is Windows-oriented, so Windows endpoints deserve first triage. But do not dismiss non-Windows install attempts. The setup path still reached out to attacker-controlled infrastructure during build, and the operator can change that remote payload without changing the PyPI version string defenders search for.

Response guidance

Treat any install or build of donutautosellsrc 0.3.7 through 0.3.9 as code execution, not as a harmless failed dependency.

  1. Isolate the affected workstation or runner.
  2. Remove the package and preserve the pip cache, temporary directories, and build logs for scoping.
  3. Rotate credentials exposed to the build context, especially PyPI tokens, Git credentials, cloud keys, and any browser- or wallet-adjacent data on Windows endpoints.
  4. Block the known staging and follow-on domains while triage is underway.
  5. Rebuild the environment from a clean baseline rather than trusting in-place cleanup of staged files.

The main lesson is straightforward. A package can look small on the registry and still act like a staged malware installer. In this case, the important code path was not “what does the package claim to do.” It was “what does the build step fetch, unpack, and hand off to next.”

From research to remediation

Check whether this pattern exists in your codebase

Turn this research into a remediation workflow. Scan dependencies and package manifests for similar supply-chain risk, then prioritize fixes with reachability context.

References