Skip to content

Back to the catalog

PyPI Access Control Vulns Patched
socket.devarticle

PyPI Access Control Vulns Patched

Socket.dev covers Trail of Bits' second external security audit of PyPI's Warehouse application, funded by the Sovereign Tech Agency, which found 14 issues including two high-severity access control flaws. One allowed any organization Member to POST an Owner-role invitation because the manage_organization_roles view gated both GET and POST behind read-only OrganizationsRead permission; the other let stale TeamProjectRole records grant upload/admin access after a project was transferred between organizations. The piece also details a Medium-severity OIDC Trusted Publishing replay window (Redis jti key expiring before PyJWT's 30s leeway), a related TOCTOU race in the same anti-replay flow, an IDOR in API token deletion, and an unfixed gap where wheel-embedded METADATA dependencies aren't validated against form-submitted metadata, letting installers see different dependency data than PyPI's JSON API/pip-audit.

TL;DR Trail of Bits' PyPI audit found org-invite privilege escalation, stale post-transfer upload access, an OIDC replay window, a token-deletion IDOR, and an unfixed wheel-metadata mismatch that can hide dependencies from security tools.
Document

What it is

Socket.dev's writeup of Trail of Bits' second Warehouse (PyPI) security audit, Feb 23–Mar 20 2026, funded by the Sovereign Tech Agency. Two consultants over six engineer-weeks produced 14 findings: 2 High, 1 Medium, 7 Low, 4 Informational. All but two are remediated. The value beyond the catalog summary is in the fix mechanics and the transferable detection patterns — this concerns a hosted service, so there is no patch for the reader to apply, but the failure modes recur in any multi-tenant authz/OIDC system.

How it works

The three main flaws are covered in the summary. The engineering lessons worth carrying:

  • Read-permission on a state-changing POST. manage_organization_roles served GET and POST under one @view_config requiring only Permissions.OrganizationsRead; the POST created invitations with an attacker-chosen role_name. Fix: split so POST requires OrganizationsManage.
  • Cross-organization stale roles. TeamProjectRole referenced project and team independently, with no constraint tying team access to the project's current owning org; ACL evaluation matched all such records without checking org ownership. Records were invisible to the receiving org and absent from the collaborator list.
  • Anti-replay key shorter than the JWT leeway. Redis jti key expired at exp + 5, but jwt.decode used leeway=30, leaving a 25-second window where the replay key was gone but the signature still validated.

What to do

The page states no commands for readers. The generalizable actions:

  • Adopt Trail of Bits' CodeQL query detecting state-changing POST handlers protected only by read-level permissions — reusable against your own codebase.
  • Add defensive ACL filters verifying a team's org matches the resource's current org, not just deleting stale rows (PyPI did both).
  • Align anti-replay key TTL with the full JWT leeway window, not exp, whenever jti single-use enforcement backs a leeway-tolerant verifier.

Caveats

Two findings remain unremediated (unnamed). PyPI audited historical records and found no evidence prior transfers left dangling permissions. Subtitle references an API-token-deletion IDOR and audit-logging gaps; the body text cuts off before detailing them.

Organisations named here

  • Alpha Omega
  • Sovereign Tech Agency
  • PEP 658

Technologies named here

  • PyPI
  • PyPI Trusted Publishing
  • pip
  • Warehouse
  • pip-audit
  • SBOM generators
  • CodeQL
  • Redis
  • PyJWT
  • OpenID Connect
  • GitHub Actions
  • Python
  • Trail of Bits

Techniques named here

  • Privilege escalation via under-permissioned write endpoint
  • Stale access control / broken ACL revalidation
  • OIDC token replay via anti-replay window mismatch
  • TOCTOU
  • IDOR