Security model and considerations
GitHub Actions' own security model for OpenID Connect tokens is a little subtle:
Any workflow defined in a repository can request an OIDC token, with any audience, so long as it has the
The claims defined in an OIDC token are bound to the workflow, meaning that a workflow defined at
org/repocannot impersonate a workflow defined at
org/repo. However, if
foo.ymlis renamed to
bar.yml, then the new
bar.ymlwill be indistinguishable from the old
bar.ymlexcept for claims that reflect the repository's state (e.g.
gitref, branch, etc.).
Generally speaking, "third party" events cannot request an OIDC token: even if they can trigger the workflow that requests the token, the actual token retrieval step will fail. For example: PRs issued from forks of a repository cannot access the OIDC tokens in the "upstream" repository's workflows.
The exception to this is
pull_request_targetevents, which are fundamentally dangerous by design and should not be used without careful consideration.
While more secure than passwords and long-lived API tokens, OIDC publishing is not a panacea. In particular:
Short-lived API tokens are still sensitive material, and should not be disclosed (ideally not at all, but certainly not before they expire).
OIDC tokens themselves are sensitive material, and should not be disclosed. OIDC tokens are also short-lived, but an attacker who successfully intercepts one can mint API tokens against it for as long as it lives.
Configuring a trusted publisher means establishing trust in a particular piece of external state; that state must not be controllable by untrusted parties. In particular, for trusted publishing with GitHub Actions, you must:
Trust the correct username and repository: if you trust a repository other than one you control and trust, that repository can upload to your PyPI project.
Trust the correct workflow: you shouldn't trust every workflow to upload to PyPI; instead, you should isolate responsibility to the smallest (and least-privileged) possible separate workflow. We recommend naming this workflow
Take care when merging third-party changes to your code: if you trust
release.yml, then you must make sure that third-party changes to that workflow (or code that runs within that workflow) are not malicious.
Take care when adding repository contributors, members, and administrators: by default, anybody who can unconditionally commit to your repository can also modify your publishing workflow to make it trigger on events you may not intend (e.g., a manual
This particular risk can be mitigated by using a dedicated environment with manual approvers, as described below.
Trusted publishers are registered to projects, not to users. This means that removing a user from a PyPI project does not remove any trusted publishers that they might have registered, and that you should include a review of any/all trusted publishers as part of "offboarding" a project maintainer.
PyPI has protections in place to make some attacks against OIDC more difficult (like account resurrection attacks). However, like all forms of authentication, the end user is fundamentally responsible for applying it correctly.
In addition to the requirements above, you can do the following to "ratchet down" the scope of your trusted publishing workflows:
Use per-job permissions: The
permissionskey can be defined on the workflow level or the job level; the job level is always more secure because it limits the number of jobs that receive elevated
Use a dedicated environment: GitHub Actions supports "environments," which can be used to isolate secrets to specific workflows. OIDC publishing doesn't use any pre-configured secrets, but a dedicated
deployenvironment is a general best practice.
Dedicated environments allow for additional protections like required reviewers, which can be used to require manual approval for a workflow using the environment.
For example, here is how
restricts reviews to members of the maintenance and admin teams:
Use tag protection rules: if you use a tag-based publishing workflow (e.g. triggering on tags pushed), then you can limit tag creation and modification to maintainers and higher (or custom roles) for any tags that match your release pattern. For example,
v*will prevent non-maintainers from creating or modifying tags that match version strings like
Limit the scope of your publishing job: your publishing job should (ideally) have only two steps:
Retrieve the publishable distribution files from a separate build job;
Publish the distributions using
By using a separate build job, you keep the number of steps that can access the OIDC token to a bare minimum. This prevents both accidental and malicious disclosure.