PyPI mandated 2FA for critical projects in July 2023, then extended the requirement to every maintainer account by January 2024. Two years later, the data is in: credential-stuffing takeovers of maintainer accounts have collapsed, but adversaries did not give up. They pivoted. Three attack patterns have absorbed almost all of the displaced activity — trusted publisher workflow abuse via leaked OIDC tokens, strategic purchase or takeover of abandoned packages, and direct compromise of maintainers' personal devices where TOTP seeds and hardware-key backups live.
This post walks through what the 2FA mandate actually changed, what it didn't, and what other registries — Maven Central, NuGet, RubyGems, crates.io — should take from the PyPI experience.
Did the 2FA mandate actually reduce account takeovers?
Yes — measurably. PyPI's 2024 transparency update reported that credential-stuffing-based unauthorized logins against 2FA-protected accounts effectively went to zero, compared to roughly 30-40 suspected takeover attempts per month in early 2023. The ctx and phpass hijacks in May 2022, which were traced to a single maintainer's reused password, are the archetypal attack this control prevented. Post-mandate, we simply have not seen a ctx-style event on PyPI.
The caveat: "account takeover" is now defined narrower than it used to be. The volume of malicious packages reaching PyPI did not drop to zero — it dropped roughly 35-50% depending on the measurement window, and the remaining volume is qualitatively different. What used to be a hijacked legitimate account pushing a backdoored release now looks like a freshly registered account typosquatting a real name, or a new maintainer added to a dormant project via a social engineering pull request.
How are attackers abusing trusted publisher and OIDC workflows?
The highest-leverage replacement attack is stealing the OIDC token a GitHub Actions runner uses to publish to PyPI. PyPI's trusted publisher flow, launched in April 2023, lets maintainers skip long-lived API tokens entirely — a CI job exchanges a short-lived OIDC assertion for a one-time upload credential. This is a net security improvement, but it moves the trust boundary into the CI workflow file.
In practice that means three new failure modes. First, a compromised third-party action — the tj-actions/changed-files incident in March 2025 is the reference case — can read the ACTIONS_ID_TOKEN_REQUEST_TOKEN environment variable and request a PyPI-scoped assertion before the legitimate publish job runs. Second, pull_request_target triggers on public repositories can be tricked into running attacker-controlled code with push privileges. Third, misconfigured OIDC subject claims — specifically, forgetting to pin workflow or environment in the PyPI trusted publisher config — let any workflow in the repo mint a publishing token.
Mitigation is unglamorous: pin every action to a commit SHA, gate publishing behind a protected environment with required reviewers, and set the PyPI trusted publisher binding to the strictest subject possible (repo:org/project:environment:release rather than repo:org/project:ref:refs/heads/main).
What happened to abandoned PyPI packages?
Attackers started buying them, or claiming them through PyPI's name-reuse policy. The economics are attractive: a package with 100,000 monthly downloads and an unresponsive maintainer is a far better distribution channel than a new typosquat. The 2024 takeover of a long-abandoned data-processing helper — reported publicly by security researchers and resolved by PyPI admins — ended with the attacker pushing a release that exfiltrated environment variables on import.
PyPI tightened its rules in response. As of 2025, a dormant project cannot be transferred to a new owner unless the requester can demonstrate both a legitimate need and that the original maintainer has been unreachable for a defined period, and any transferred project gets flagged in the web UI and feed. That helps humans, but it does not help the build server that just installed requirements.txt and got a new-maintainer version. Dependency consumers still need to pin versions, monitor for maintainer changes, and treat a sudden ownership change the same way they treat a new transitive dependency.
Are maintainer devices the new weak point?
Yes, and this is the hardest problem to solve at the registry level. If an attacker has code execution on the maintainer's laptop, TOTP seeds stored in a desktop authenticator, WebAuthn platform authenticators unlocked by the OS, and any long-lived session cookies are all in reach. The October 2024 lottie-player npm incident — not PyPI, but directly analogous — originated from a maintainer's compromised browser session, not a password or a 2FA bypass.
The practical lessons are boring and well-known: hardware security keys that require physical touch per authentication, a dedicated publishing machine or at least a dedicated browser profile, short session lifetimes on the registry side, and release signing that happens in a CI environment the maintainer does not log into interactively. PyPI added support for attestations via Sigstore in 2024, which moves one more piece of the trust out of the maintainer's daily workstation.
What should other registries take from this?
Copy the mandate, but plan for the pivot. Maven Central's ongoing migration to the new Central Portal has made 2FA a practical requirement for namespace claims, but the publishing flow still accepts long-lived Sonatype tokens, which recreates exactly the API-key exposure PyPI is actively trying to retire. RubyGems rolled out mandatory MFA for top-100 gem owners in 2022 and broadened it through 2023, with similar takeover-reduction results, but RubyGems does not yet have a trusted-publisher equivalent, so the OIDC-token-theft class is largely theoretical there — for now.
Three design decisions would save the next registry the two-year learning curve PyPI just paid for. Bake OIDC-based publishing in from day one, with strict subject claims and required protected environments. Treat project transfer as a high-risk event with a mandatory cooling-off period and an obvious public signal to consumers. And publish the same kind of transparency data PyPI has — monthly counts of takeover attempts, malicious uploads caught, and average time to removal — so the ecosystem can actually tell whether new controls are working.
Does 2FA still matter if the attack has moved?
It absolutely does. Removing one class of attack doesn't make the control redundant — it makes the remaining classes more visible. A world without the 2FA mandate would layer OIDC-token theft and abandoned-package acquisition on top of credential-stuffing takeovers, not instead of them. The July 2023 and January 2024 decisions closed the cheapest attack path, and every registry that has not yet done the same is currently carrying that debt.
The secondary benefit is cultural. "You must have 2FA" has turned into "you should have a hardware key," which is turning into "your release pipeline should not depend on your laptop being uncompromised." That is the arc other ecosystems need to accelerate.
How Safeguard.sh Helps
Safeguard.sh's reachability analysis tells you whether a newly-published PyPI version — including one pushed by a freshly-transferred owner or an OIDC-abuse publish — actually touches your running code, and typically cuts 60-80% of CVE noise so the genuine risks surface. Our SBOM ingestion captures maintainer identity, publisher trust-level, and package provenance for every dependency, so a silent maintainer-change on an abandoned package becomes a build-time alert rather than a post-incident forensic finding. Griffin AI handles autonomous remediation — pinning, patching, or swapping a compromised package across every affected service — while the TPRM module extends the same scrutiny to vendor-supplied Python wheels. The 100-level dependency depth scan catches malicious transitive pulls that post-install scripts or optional extras try to hide, and container self-healing rolls affected workloads back the moment a publish-event triggers a high-severity signal.