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
| Package | Malicious version(s) | Clean upgrade line |
|---|---|---|
donutautosellsrc | 0.3.7, 0.3.8, 0.3.9 | None 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:
- the code is described as firing during both normal install and PEP 517 wheel build
- the downloaded object is not a direct
.zipor.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.pymisses the bundled application contents - reviewing only Python files misses the
app.pydnative 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.
- Isolate the affected workstation or runner.
- Remove the package and preserve the pip cache, temporary directories, and build logs for scoping.
- Rotate credentials exposed to the build context, especially PyPI tokens, Git credentials, cloud keys, and any browser- or wallet-adjacent data on Windows endpoints.
- Block the known staging and follow-on domains while triage is underway.
- 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.