- Attackers increasingly abuse PyPI to ship multi‑stage malware, from W4SP Stealer copycats to full RATs hidden in resources, loaders and setup scripts.
- Campaigns combine social engineering, typosquatting, persistence tricks and creative obfuscation to steal credentials, crypto assets and gain remote access.
- Defenses rely on static heuristics, runtime analytics and rapid registry response, but also on stricter dependency hygiene and manual review in development teams.
Over the last few years, PyPI has gone from being “just” the central Python package index to one of the hottest targets in the software‑supply‑chain battleground. Attackers have realized that if they can slip malicious code into a popular dependency – or into a convincing fake – they instantly gain a shortcut into thousands of developer laptops, CI pipelines and production environments. From basic credential‑stealers to full‑blown remote access trojans (RATs), PyPI has hosted a surprising variety of malware campaigns.
This article walks through the main families of real‑world attacks observed in PyPI and related ecosystems, how the payloads work, and the defensive tooling and processes that are emerging to keep them in check. We will look at copycat W4SP Stealer campaigns, multi‑stage loaders abusing setup.py, backdoors such as SilentSync, creative data‑hiding tricks like steganography or dictionaries that double as RAT downloaders, and the growing ecosystem of scanners such as Prisma Cloud and Datadog GuardDog. The goal is to understand the patterns so that developers and security teams can spot the next campaign even if the specific package name is new.
Why PyPI is such a juicy target for malware authors
PyPI’s success is exactly what makes it so attractive for threat actors. It is the de‑facto public registry for Python code, anyone can publish a package in minutes, and in most workflows dependencies are installed and updated automatically via tools like pip, CI pipelines and automated dependency bots. There is no mandatory manual review or hard security gate before code hits end‑user environments.
Malicious packages can enter the ecosystem through multiple routes. Some are published as brand‑new projects with no history, others hijack abandoned names or compromised maintainer accounts, and many rely on social engineering or typosquatting to trick users into running pip install <package>. Once installed, the malware typically executes immediately on installation (via setup.py, wheels or post‑install hooks) or on first import through __init__.py or apparently harmless helper modules.
The impact ranges from stealthy data theft to full system compromise. PyPI malware has stolen browser passwords and cookies, Discord tokens, cloud credentials and crypto‑wallet seeds, but it can also drop binaries, add persistence via scheduled tasks or autostart entries, open command‑and‑control (C2) channels and effectively turn developer machines into remotely controlled footholds inside organizations.
Because dependencies are deeply embedded into build and deployment pipelines, a single poisoned package can cascade across projects. Automated upgrades and lax review of dev‑only dependencies have already led to situations where a malicious update to a seemingly harmless configuration or linting package propagated through thousands of repositories before being noticed.

W4SP Stealer copycats and data‑harvesting campaigns in PyPI
One of the most visible PyPI malware stories has been the spread of W4SP Stealer and its many imitators. In March 2023, researchers from Unit 42 (Palo Alto Networks) uncovered six Windows‑focused malicious packages in PyPI whose second stage was a configured build of W4SP Stealer 1.1.6. The attackers clearly borrowed from prior public campaigns, but with less sophistication in some areas.
These packages had a series of red‑flag traits in their metadata and behavior. They had no linked GitHub repositories, only a handful of downloads, newly created authors with no prior contribution history, and usernames following a pattern such as Anne1337 or Richard1337, all created within minutes of each other. Each package pointed to a suspicious remote URL on a paste site that served the real payload when the package was installed.
The entry point of the attack was a custom setup.py that executed at install time rather than at import time. The script downloaded Python code from a remote URL using urllib.request.urlopen, wrote it to a temporary file with NamedTemporaryFile, then executed it via exec and attempted to launch it using the Windows start command and pythonw.exe. Using pythonw.exe avoided SmartScreen prompts because the script was not a directly downloaded executable, allowing the malware to run quietly on Windows machines.
Once launched, the W4SP Stealer component focused on extracting highly sensitive data. It used Windows DPAPI via CryptUnprotectData to decrypt stored secrets, traversed browser profiles for Chrome and Edge to steal cookies and saved logins, accessed SQLite databases backing browser storage, and exfiltrated everything to a Discord webhook controlled by the attacker. Tokens were validated using the Discord API, and the code handled failures gracefully to keep harvesting even when individual operations broke.
These techniques are not unique to one campaign; other large‑scale infections repeat the same patterns. ESET, for example, reported a separate wave of at least 116 malicious PyPI packages uploaded under 53 projects, downloaded more than 10,000 times. That campaign mixed backdoors written in Python and Go for Windows and Linux, clipboard monitors that substituted crypto‑wallet addresses using the pyperclip library, and payloads that again included W4SP Stealer variants alongside custom spyware.
Attackers used multiple packaging tricks to hide the malicious content. In some ESET‑observed cases, the source distribution was clean while the wheel contained the malware, exploiting the fact that pip prefers wheels over source by default. Elsewhere, a test.py module embedded slightly obfuscated code and was quietly imported from __init__.py, ensuring automatic execution whenever the package was imported.

Setup.py abuse, multi‑stage loaders and cross‑platform persistence
Many PyPI malware families lean heavily on setup.py as the first‑stage loader. Because the legacy packaging format runs the setup.py script on the local machine during installation, it is an ideal place to sneak in malicious behavior that users rarely inspect. Attackers have exploited this by embedding PowerShell, downloaders and even fully fledged persistence logic directly in the installation script.
One common pattern is a PowerShell or shell one‑liner that fetches and runs the next stage. ESET documented packages whose setup.py invoked a PowerShell script to download a ZIP archive (for example from transfer.sh), extract it into a location such as C:\ProgramData, install Python dependencies and then execute a script like server.pyw. On Linux, variants instead downloaded ELF binaries or Python scripts and appended launch commands to .bashrc, ensuring execution on every new shell.
Some campaigns dispense with any legitimate code entirely and ship only obfuscated malware. Instead of pretending to be real utilities, these packages simply write temporary Python files with embedded payloads and execute them via pythonw.exe or equivalent, often followed by deleting visible traces while keeping persistence entries active.
Persistence techniques vary by platform but aim for stealth and longevity. On Windows, researchers have seen encoded VBScript files (for example pythenenv.vbe under %APPDATA%/Pythonenv) scheduled via the Task Scheduler as innocuous‑looking tasks such as MicrosoftWinRaRUtilityTaskB, running every few minutes. On Linux, autostart entries are dropped into ~/.config/autostart under names like mate-user-share.desktop that mimic desktop environment components, and executables are hidden in paths resembling KDE configuration directories.
Behind these loaders sits either a custom backdoor or a well‑known stealer. Windows backdoors commonly open TCP sockets to a hardcoded domain, send machine identifiers (hostname, MAC address, username), then wait for commands to execute and return output. Linux backdoors written in Go follow similar patterns with binary protocols. In some variants, the final payload is again W4SP Stealer or a crypto‑clipboard hijacker that swaps Bitcoin, Ethereum, Monero or Litecoin addresses in the clipboard with attacker‑controlled ones.
Creative loaders: hidden payloads in dictionaries, DLL side‑loading and more
As defenders tighten detection around obvious exec patterns, attackers are getting more inventive with how they hide and trigger payloads. Several recent campaigns illustrate how far they are willing to go to evade both static and simple heuristic analysis while still leveraging PyPI as the delivery channel.
One striking example involves malicious spell‑checker packages that bury a RAT loader inside a language dictionary file. Two PyPI packages, spellcheckerpy and spellcheckpy, impersonated the legitimate pyspellchecker project, even linking to its real GitHub repository. Inside a compressed JSON resource for the Basque language (resources/eu.json.gz), the attackers added a key named spellchecker whose value was a base64‑encoded Python downloader.
The utility function that extracted dictionary data looked perfectly normal at first glance. It built a path to resources/<lang>.json.gz, decompressed it via gzip.open, parsed the JSON with json.loads, and returned data[index]. However, when invoked with test_file("eu", "utf-8", "spellchecker"), it returned the hidden base64 payload instead of word frequencies, which was then decoded into stage‑1 malware.
Early versions of the packages kept the payload dormant, decoding but never executing it. That changed in version 1.2.0, where the trigger moved into the WordFrequency.__init__ method. The code reconstructed the string "exec" from the hex sequence 65786563, compiled the downloaded code using compile, then ran it with eval. This obfuscation step was enough to bypass many static rules that simply searched for literal calls to exec.
The stage‑2 payload was a full‑featured Python RAT controlled via HTTPS. The first stage fetched code from https://updatenet[.]work/settings/history.php, spawned it in a detached python3 - process with start_new_session=True so that it outlived the host script, and never wrote it to disk. The RAT fingerprinted the system (OS version, hostname), used a dual‑layer XOR scheme to obfuscate traffic, and implemented a simple binary command protocol with IDs such as 1001 for arbitrary code execution via exec. It beaconed every few seconds to https://updatenet[.]work/update1.php, ignoring certificate validation.
The infrastructure behind the C2 domain tied back to known malicious hosting providers. The domain resolved to an IP in a range associated with Cloudzy (RouterHosting), a provider that prior research has described as a de‑facto C2 haven used by multiple APT groups, ransomware operators and spyware vendors. A previous campaign documented under a slightly different package name reused the same RAT design but with another C2 domain, suggesting ongoing iterations by the same actor.
Other ecosystems show equally creative multi‑stage designs that developers should keep in mind when reviewing PyPI dependencies. For instance, the termncolor package in PyPI pulled in a malicious dependency colorinal that performed DLL side‑loading on Windows. It dropped a legitimate binary (vcpktsvr.exe) and a fake libcef.dll, relying on Windows’ DLL search order to load the rogue DLL, which then handled persistence, system reconnaissance and C2 communications via Zulip (an open‑source chat platform) to blend into innocuous traffic. A Linux variant used a shared object named terminate.so to implement the same behavior.
Impersonation and government‑themed lures: the SilentSync RAT case
Not all malicious packages pose as generic utilities; some go out of their way to imitate specific legitimate APIs and workflows. Zscaler ThreatLabz analyzed two PyPI packages, sisaws and secmeasure, that ultimately deployed a cross‑platform RAT named SilentSync. Both were uploaded by the same author and shared overlapping metadata.
The sisaws package mimicked a real Argentine health‑information client library called sisa. The genuine sisa package provides puco and renaper modules that wrap public government APIs for verifying national ID numbers, health coverage and personal details. The malicious clone copied the structure and behavior at a surface level: it validated DNI formats, returned structured dictionaries that looked like API responses and even included realistic‑looking user records under msal.gov.ar email addresses.
Hidden inside the initialization script was a backdoor function gen_token that doubled as a malware downloader. This function accepted a single hardcoded token value; any other input triggered an error. If the correct magic token was supplied, gen_token returned a fabricated “success” response containing user fields and a secondary static token. Under the hood, it decoded a hex string into a curl command that silently downloaded a Python script from Pastebin (https://pastebin.com/raw/jaH2uRE1) into a temporary helper.py file and executed it with Python. That script was SilentSync.
The related package secmeasure was marketed as a “string cleaning and security” helper but reused the same infection logic. On the surface it offered typical sanitization helpers – stripping whitespace, removing special characters, escaping HTML, normalizing Unicode, sanitizing shell commands and decoding hex. In practice, several of these functions raised NameError exceptions due to missing imports, and a function called sanitize_input executed the same hex‑encoded curl chain used by sisaws to deploy SilentSync.
Once on disk, SilentSync established persistence and a simple HTTP‑based C2 channel across operating systems. On Windows it added a PyHelper entry under HKCU\Software\Microsoft\Windows\CurrentVersion\Run, on Linux it wrote a @reboot cron entry, and on macOS it created a com.apple.pyhelper.plist launch agent in the user’s ~/Library/LaunchAgents. The C2 IP address – 200.58.107[.]25 – was base64‑encoded in the code and decoded at runtime, communicating over HTTP on port 5000 using REST‑style endpoints such as /checkin, /comando, /respuesta and /archivo.
SilentSync supported a compact but dangerous command set. It could execute arbitrary shell commands (cmd), exfiltrate individual files or whole directories (compressing the latter into ZIPs), capture screenshots (screenshot), upload artifacts and steal browser data for Chromium‑based browsers and Firefox under the browserdata action. After exfiltration it cleaned up temporary files to reduce forensic artifacts.
Broader supply‑chain risks and detection tooling for PyPI malware
The incidents above sit within a much wider trend: open‑source package registries are being systematically probed as a supply‑chain entry point. Beyond PyPI, npm has seen poisoned packages distributed as “proof‑of‑concept” exploits, job‑assessment exercises and fake kernel patches that quietly harvest iCloud Keychain data, browser credentials, crypto wallets, keystrokes and screenshots. Attacks such as the eslint-config-prettier compromise have shown how automated dependency updaters can turn a single compromised project into a mass infection vector.
To respond, both registry maintainers and security vendors are rolling out increasingly sophisticated detection mechanisms. PyPI itself introduced a “Report project as malware” button on project pages, visible to logged‑in users, and is piloting a malware‑reporting API designed to shorten the time between discovery and takedown. Administrators review incoming reports, may request additional details such as precise file locations or code lines, and then remove malicious projects and users when abuse is confirmed.
Cloud‑security platforms like Prisma Cloud provide another layer of protection by scanning open‑source dependencies before and during runtime. Prisma’s malicious‑package detection engine looks for suspicious attributes such as newly created authors with no history, missing project metadata, remote URLs embedded in setup.py or package code, and code patterns associated with known stealer or backdoor families. Discovered threats receive internal Prisma identifiers (Prisma‑IDs), even if there is no public CVE, and show up in the Prisma CVE Viewer so customers can trace and remediate affected workloads and pipelines.
Runtime defenses complement pre‑deployment scanning by watching for anomalies in how code behaves once deployed. Prisma Cloud’s runtime features and Cortex XDR’s EDR analytics, for example, monitor for unexpected processes, network connections to rare external hosts, or the appearance of known malware binaries inside containers and VMs. Analytics rules such as “UNIX LOLBIN process connected to a rare external host” can surface situations where a benign‑looking interpreter suddenly becomes a C2 beacon for a malicious PyPI dependency.
On the static‑analysis side, Datadog’s GuardDog project showcases how rule‑based heuristics can catch many classes of malicious PyPI packages. GuardDog uses Semgrep rules to scan both source code and PyPI metadata, focusing not on signatures but on generic attacker behaviors observed across dozens of real packages. Because the rules look for data‑flow patterns and unusual configurations, they can flag unknown malware families that reuse the same techniques.
GuardDog’s code heuristics concentrate on a handful of high‑signal behaviors. They include overwriting the install command in setup.py to run system commands on install, dynamically executing base64‑encoded data via exec or eval, extracting and running code hidden in images (steganography), downloading executables from HTTP responses and running them, and exfiltrating sensitive data such as AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, hostnames or .aws/credentials contents to remote servers.
Metadata‑level heuristics cover tricks that do not show up in code alone. GuardDog checks for package names with small Levenshtein distance to the top 5,000 packages or simple character swaps (classic typosquatting), maintainers whose email domains were re‑registered after the latest release (potential account takeover), and suspiciously empty metadata such as a 0.0.0 version and no description. While such signals are not proof of malware, they help triage which projects deserve manual review.
Real‑world use of GuardDog quickly surfaced a long list of malicious PyPI packages with diverse payloads. Some, like beautifulsup4, mimicked the name of popular libraries (in this case beautifulsoup4) and installed a Windows browser extension under the user’s APPDATA directory. That extension requested clipboard permissions and periodically checked for crypto‑wallet addresses, replacing them with attacker‑controlled ones by editing pinned browser shortcuts and injecting --load-extension flags.
Other packages used heavy obfuscation and steganography to conceal multi‑stage downloaders. One sample imported a library like judyb, fetched an image from Imgur, then called LSB.reveal() to extract hidden code from the PNG and immediately executed it. The recovered payload then wrote a temporary script that downloaded yet another stage from a domain such as misogyny.wtf and ran it via a background Python process. At each step, code was kept in memory or short‑lived temp files to leave minimal traces.
Stealer families also show up repeatedly in GuardDog’s findings. Packages rebranded as framework utilities or HTTP clients turned out to be wrappers for cookie stealers targeting Roblox sessions, Discord token harvesters that uploaded data to webhooks, or droppers that downloaded PyInstaller‑packed binaries from public GitHub repositories. Those binaries in turn walked through Chrome, Firefox, Brave and other browser profiles to collect cookies and credentials, sending them to attacker webhooks for account takeover and fraud.
Registry maintainers like PyPI emphasize that the presence of malware in projects is an ecosystem issue, not a core platform vulnerability. PyPI’s backend software has been externally audited and assessed as conforming to widely accepted best practices. The weak points are the human processes wrapped around dependency selection and review: anyone can publish, and developers routinely add dependencies on the basis of a brief README or a code snippet in a blog post without inspecting the package internals.
Improving security therefore hinges on a blend of better tools, stricter processes and ongoing community vigilance. Automated static and runtime scanning helps catch many bad packages, but organizations still need guardrails such as internal mirrors of vetted dependencies, mandatory code review for new third‑party libraries, locked dependency versions, and clear response plans for when a malicious dependency is discovered. At the same time, security teams benefit from public research reports, IoC feeds and ATT&CK mappings that describe how specific campaigns operate so they can tune detection and hunt for related traces.
Ultimately, understanding how malware embeds itself into PyPI packages – from sneaky setup.py scripts and fake government APIs to RATs hiding in compressed dictionaries – gives teams the context they need to push back effectively. By combining ecosystem‑level protections, targeted tools like GuardDog and Prisma Cloud, and a healthy skepticism toward new dependencies and unsolicited “assessment” tasks, organizations can significantly reduce the odds that a simple pip install ends up opening a backdoor into their most sensitive environments.