PyPI Fixed High-Severity Access Control Bugs Found in a Warehouse Security Audit

A Trail of Bits audit of PyPI’s Warehouse reported two high-severity access-control issues and an OIDC Trusted Publishing replay edge case. PyPI says it fi

2026-05-06 GIGATAP Team #security
#pypi#python#supply chain security

PyPI has remediated two high-severity access control issues identified in its second security audit of Warehouse, the open source application that powers package uploads and distribution for the Python ecosystem.

The review was performed by Trail of Bits and focused on authentication/authorization, organization permission boundaries, the OIDC “Trusted Publishing” pipeline, package upload and metadata validation paths, API token lifecycle operations, and audit logging.

Trail of Bits reported 14 findings in total: two High, one Medium, seven Low, and four Informational. PyPI said all but two findings have been remediated.

What Was Audited (And Why It Matters)#

Warehouse is the control plane for PyPI: it handles who can upload packages, how metadata is validated, how projects move between organizations, and how API tokens and Trusted Publishing flows translate identity into permission.

Those are high-leverage surfaces. Most supply-chain incidents don’t require a novel crypto break. They require a path where the system grants the wrong capability to the wrong identity, or where old capabilities don’t get revoked when ownership changes.

Trail of Bits conducted the review from February 23 to March 20, 2026, with two consultants working over six engineer-weeks, according to the write-up.

High-Severity Finding 1: Organization Members Could Invite Owners#

The first high-severity issue involved PyPI’s organization role management flow.

Trail of Bits found that a view named manage_organization_roles handled both GET and POST requests under a single permission check labeled Permissions.OrganizationsRead. On POST, that same view called an invitation function (_send_organization_invitation) and created an invitation with the submitted role name.

Because the endpoint required only “read” permission, an organization member could submit a POST request that invited a new user with a higher role than the inviter should be able to grant (including Owner, Manager, or Billing Manager).

Trail of Bits described an exploit scenario where a user with the Member role sends an invitation assigning the Owner role to a colluding account. If accepted, that invitation would grant administrative control over the organization, including the ability to add/remove projects, manage trusted publishers, modify billing, and potentially remove the original owner.

PyPI’s reported fix was to split the view configuration so GET requests require read-level permission, while POST requests require an appropriate write-level permission.

Trail of Bits also developed a custom CodeQL query to detect similar patterns (state-changing POST handlers protected only by read-level permissions). That’s a useful takeaway beyond PyPI: “method-based authorization mismatch” is a common failure mode in web apps, especially when a single view function handles multiple HTTP methods.

High-Severity Finding 2: Project Transfers Could Leave Stale Upload Access#

The second high-severity issue was about project transfers between organizations.

Trail of Bits reported that when a project was transferred or removed from an organization, PyPI deleted the organization_project junction record but did not clean up other organization-scoped permission records associated with that project.

The underlying issue, as described, was that the model referenced projects and teams independently, without a constraint tying a team’s project access to the organization that currently owned the project. During access control evaluation, PyPI would query all team records for a project and grant permissions to members of each matched team without verifying that the team belonged to the project’s current organization.

That creates a “dangling permission” risk: stale team roles could continue granting access after a project changes organizations.

Trail of Bits noted the impact depended on the stale role:

  • A stale Owner-level role could grant administrative permissions.
  • A stale Maintainer role could grant upload permissions, which Trail of Bits described as sufficient to push malicious releases.

The write-up emphasized that this stale access could also be difficult to detect: the records were reportedly not visible to the receiving organization, did not appear in the project’s collaborator list, and were resolved only during ACL evaluation.

In a scenario described in the report, an attacker who was part of a “release-engineers” team in the departing organization could retain Owner-level access after the project was transferred, then upload a backdoored release using the retained upload capability.

PyPI’s reported remediation had two parts:

  • Delete records belonging to the departing organization before deleting the junction record.
  • Add defensive ACL filters that verify a team’s organization matches the project’s current organization before granting team permissions.

PyPI also stated it audited database records and found no evidence that previous transfers had resulted in dangling permissions.

Trusted Publishing: A Replay Edge Case Around Expiration#

Trail of Bits also found two issues in PyPI’s Trusted Publishing flow, which uses OIDC JWTs from CI providers (including GitHub Actions and GitLab CI) to mint short-lived upload tokens.

The Medium-severity issue described in the source involved the JWT Token Identifier claim (jti), which is intended to make each OIDC token single-use.

According to the write-up:

  • PyPI stored each jti value in Redis after minting a macaroon and checked whether it already existed before accepting a new token.
  • The Redis key TTL was set to the token expiration timestamp.
  • The JWT library accepted tokens up to 30 seconds past expiration due to a leeway setting.

Trail of Bits concluded that this combination created a short window where Redis had already evicted the anti-replay key but the JWT could still pass verification, enabling a replay of a token that should have been single-use.

The source material provided here truncates before describing the full attacker precondition and exploitation path, so it’s important not to overstate what was demonstrated. The key point is the class of bug: replay prevention that depends on storage TTL must account for any verification leeway and clock skew, otherwise the “single-use” guarantee can quietly fail at the boundary.

Other Remediations Mentioned (And One Gap That Remains)#

Beyond the two high-severity issues and the Trusted Publishing replay edge case, the source notes other remediated findings including audit logging gaps and an IDOR (insecure direct object reference) in API token deletion.

It also highlights a “wheel metadata validation gap” that remains open, and that two total findings were not remediated at the time of writing. The provided excerpt does not include the full details of those remaining items, so readers should refer to the original source and the underlying audit report for specifics.

One broader observation from the write-up is that “inconsistent authorization enforcement” was a common pattern across findings. That matches what many audits surface: the hardest bugs are not missing authentication, but small inconsistencies in how a system applies authorization checks across endpoints, methods, and object lifecycles.

Practical Takeaways For Maintainers And Security Teams#

If you maintain a package registry, an internal artifact repo, or any multi-tenant developer platform, the PyPI findings map cleanly to checks you can run in your own environment:

  • Treat HTTP method splits as separate authorization problems. A single view that handles both read and write paths is a common place for permission drift.
  • Make “ownership changes” a first-class lifecycle. When a project/org relationship changes, revocation must clean up derived permissions (teams, tokens, scoped roles), not just the direct link.
  • In ACL evaluation, bind permissions to the current parent object. If a team grants access to a project, ensure the team belongs to the organization that currently owns that project.
  • If you rely on “single-use” OIDC tokens, align anti-replay storage TTL with verification behavior. Account for leeway and potential clock skew so that the anti-replay key outlives any token acceptance window.
  • Audit logging isn’t just visibility; it’s part of containment. Gaps in logging can turn a small access-control bug into a long-lived blind spot.

For organizations that consume PyPI packages, the operational takeaway is narrower but still real: registry-side hardening reduces risk, but it doesn’t eliminate it. Keep dependency intake controls (pinning, review gates for high-risk packages, and monitoring for unexpected release events) aligned with your threat model.

Source#

Socket Blog: https://socket.dev/blog/pypi-fixes-high-severity-issues-found-in-security-audit?utm_medium=feed