In March 2022, an attacker exploited an expired maintainer domain to take over the ctx package on PyPI and push a malicious update that quietly exfiltrated environment variables — including AWS credentials — from every machine that ran pip install --upgrade ctx. Researchers tracking the incident found a nearly identical compromise hit the PHP package phpass the same week, planted by the same actor. That single episode captures why Python's supply chain has become one of the most targeted attack surfaces in software: PyPI now hosts more than 500,000 projects and serves billions of downloads a month, publishing requires no vetting, and pip install can execute arbitrary code before a human ever reviews it. This post walks through how these attacks actually happen, why a bug from 2007 was still exploitable in 2023, and what engineering teams can do about it right now — and where the common python security tools do and don't help.
What Makes Python's Dependency Ecosystem So Exposed?
Python's exposure comes from a combination that most other ecosystems don't share at the same scale: install-time code execution plus an open, unvetted namespace. When pip installs a package distributed as a source distribution (sdist), it runs the package's setup.py file directly on the host — no sandbox, no review, no distinction between "build instructions" and "arbitrary Python code." A malicious setup.py can read SSH keys, exfiltrate environment variables, or drop a reverse shell during pip install, well before your application ever imports the library. Layer on top of that a registry with no identity verification at signup and a flat namespace where anyone can claim reqeusts or python3-dateutil, and you get a platform where a typo, a stale dependency pin, or a single compromised maintainer account can propagate malicious code into thousands of downstream projects within hours.
How Have Attackers Actually Compromised PyPI Packages?
Attackers have used three repeatable playbooks: account takeover, typosquatting, and phishing. The ctx/phpass incident above is the clearest example of account takeover via infrastructure decay — the maintainer's email domain lapsed, the attacker re-registered it, reset the PyPI password, and shipped malware to legitimate users of an already-trusted package. Typosquatting is older and cheaper: packages like colourama (playing off colorama) have shipped Windows clipboard-hijacking malware to developers who mistyped a single character. And in 2022, security researchers at Checkmarx documented the "JuiceLedger" campaign, in which attackers sent PyPI maintainers phishing emails posing as account-verification requests; maintainers who entered credentials on the fake page had their real packages hijacked and re-published with malware, no typo required. Separately, Checkmarx and other researchers tracked dozens of PyPI packages in late 2022 distributing the "W4SP Stealer," malware built to lift Discord tokens, browser-saved passwords, and cryptocurrency wallet files from developer machines that had simply run pip install on a package with an innocuous-sounding name.
Why Did a 15-Year-Old Bug Stay Exploitable Until 2023?
Because the original 2007 disclosure recommended a fix that Python never enforced by default. CVE-2007-4559 describes a path-traversal flaw in Python's tarfile module: TarFile.extractall() will happily write files to paths defined inside the archive, including ../../etc/cron.d/malicious or absolute paths outside the intended extraction directory. The original bug report suggested validating member paths before extraction, but the Python core team left the unsafe behavior as the default rather than breaking backward compatibility. In December 2022, Trellix researchers reported that this "fixed" 15-year-old bug was still present and estimated it affected more than 350,000 public GitHub repositories that called extractall() without validation. Python only closed the gap by default with PEP 706, which added a filter argument to tarfile extraction methods and shipped in Python 3.12 in October 2023 — meaning every project still running 3.11 or earlier is exposed unless it explicitly passes filter="data".
Can pip and PyPI's Own Defenses Stop These Attacks?
Partially, and the gaps matter more than the fixes. PyPI has made real improvements: by the end of 2023 it required two-factor authentication for every account maintaining a package, a mandate the Python Software Foundation extended to all PyPI accounts at the start of 2024. In April 2023, PyPI also rolled out Trusted Publishing, an OIDC-based mechanism that lets CI systems like GitHub Actions publish packages without long-lived API tokens sitting in repo secrets — removing a favorite target for credential theft. But pip itself still does not verify package signatures or provenance by default. Hash-pinning exists (pip install --require-hashes) but is opt-in and rarely used outside regulated environments, and PyPI has no equivalent to npm's package provenance attestations enforced at install time. In practice, 2FA and trusted publishing raise the bar for account takeover, but they do nothing to stop a legitimately-published, never-compromised package from being malicious, over-permissioned, or simply vulnerable code your own scanners never flagged because it's never actually reachable from your application's execution paths — or flagged everything equally because they can't tell.
What Should Python Teams Actually Do to Reduce Risk?
Teams should treat every pip install as code execution, not configuration, and build controls around that reality. Concretely: pin dependencies by hash in a lockfile (pip-tools, poetry.lock, or pdm.lock) rather than loose version ranges, so a compromised release of an already-installed package can't silently slip into a rebuild. Prefer wheels over sdists where possible, since wheels are pre-built and don't execute setup.py on install. Route installs through an internal package proxy (e.g., a private PyPI mirror or Artifactory) so you can quarantine new releases before they reach developer machines and CI runners. Generate and retain a Software Bill of Materials (SBOM) for every build so you have an authoritative, queryable record of exactly which package versions shipped — critical when the next ctx-style compromise hits and you need to know in minutes, not days, whether you're affected. And prioritize remediation using reachability rather than raw CVE counts: a critical vulnerability in a transitive dependency's code path that your application never calls is a very different risk than one sitting directly in your request-handling logic. Most python security tools on the market still rank findings by raw CVSS score alone, which is exactly the gap reachability-aware scanning is meant to close.
How Safeguard Helps
Safeguard is built around the gap that pure CVE scanning leaves open: knowing which vulnerabilities in your Python dependency tree are actually reachable from your application code, not just present in a requirements.txt. Our reachability analysis traces call paths from your entry points through transitive dependencies so teams can separate "must fix this week" from "safe to schedule," cutting through the alert noise that makes teams numb to scanner output in the first place. Griffin AI, our AI-assisted triage engine, correlates that reachability data with exploit context to explain why a given CVE matters in your specific codebase, in plain language a developer can act on without a security background. Safeguard generates SBOMs automatically on every build and ingests existing SBOMs from your CI pipeline, giving you the auditable record you need when a supply chain incident — like the ctx or W4SP Stealer campaigns above — requires a fast answer to "are we exposed?" And where a fix exists, Safeguard opens an auto-fix pull request with the patched version and dependency graph already resolved, so remediation is a review-and-merge instead of a multi-day manual bump-and-test cycle.