Description
This is a summary of my own notes from conversations with @di concerning Warehouse's threat model around OIDC (and GitHub, in particular, as an OIDC provider).
JWT considerations
JWT reuse
Warehouse needs well-defined and well-documented behavior around handling of a JWT seen more than once. For example, a GitHub action might do the following (pseudocode):
mint a JWT
for each dist in dist/* {
use the JWT to mint an access token
upload dist with the access token
}
In this case, the JWT gets used N
times: once for each access token minting. This is probably not what we want; instead, we want this:
mint a JWT
mint an access token using the JWT
for each dist in dist/* { ... }
In this use pattern, we invalidate the JWT in Warehouse's backend after its first use. The process for that is probably as simple as performing uniqing on the jti
claim after JWT verification, and then adding (jti, exp)
to some table, where exp
is the JWT's expiration claim (which gives us the ability to automatically clear out old JWTs once they expire).
Non-JWT considerations
Access token ephemerality
We should determine exactly how long-lived our ephemeral access tokens/API keys will be: how long is a reasonable period to allow uploads for? Should we cap the number of individual requests at some high number (e.g., is there any legitimate packaging workflow that creates more than 100 separate distribution files and uploads them)?
Provider-specific considerations
GitHub: account resurrection/reuse
GitHub allows a deleted user's username to be reused. When this happens, JWTs minted by the new user are indistinguishable from JWTs minted by the old one. This is a potential problem if PyPI is configured to accept JWTs from user/repo @ workflow
, where user
changes hands on GitHub -- the new (malicious) user
would still be able to authenticate with PyPI as if they were the old user
.
We probably need a mitigation for this before we fully enable support for GitHub as an OIDC provider. Two potential solutions:
- Verify the
repository_owner
claim in the JWT with some sidecar information: on trusted setup, retrieve the GitHub User ID (which is hopefully unique) for the user and cross-check it against the current ID. This is not the preferred solution. - Get GitHub to include a
repository_owner_id
claim in the JWT, or something similar (like arepository_owner_epoch
, if they don't want to guarantee the ID's uniqueness).
Neither of these is a great solution, because both still require some amount of sidecar state: we still have to either initially store the GitHub user's ID during trusted setup or during TOFU, so that we can check for changes during subsequent authentications. That complicates our DB schema, which we'd like to keep generic.
GitHub: JWTs minted for other consumers on the same workflow
GitHub allows any action to mint a JWT. Warehouse's consumer will only accept JWTs with acceptable claims (e.g., a matching ref
), but that might not be sufficient.
In particular, it's conceivable for a publish
workflow to have two jobs: publish-pypi
and publish-rubygems
, both of which mint JWTs for their respective services. Currently, there is no way for Warehouse to distinguish between these two JWTs: both originate from the same workflow. We could distinguish them with the aud
claim, which Warehouse would then filter on, but that claim does not provide authenticity: an attacker who manages to take over the publish-rubygems
job could mint a JWT with aud=pypi
. We should determine whether this is a situation we need to handle on Warehouse's side.
GitHub: access token leakage
We should make sure that any (official) actions that mint access tokens via an OIDC JWT add the access token as a "mask value" so that it does not appear in CI logs: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#masking-a-value-in-log
GitHub: trusted workflows
Warehouse has no visibility into workflow access: a user could misconfigure their GitHub Actions such that anybody can run a publish/release action and thereby publish malicious distributions. We can't prevent this, but we should probably provide documentation/guidance to steer users in the right direction.
This is true also for trusted branches/tags: anybody who can push a new tag to the repo could conceivably authenticate with PyPI if the action is e.g. configured to match against tags like v*
.