Incident Analysis

PyPI Trusted Publishing Token Leaks in 2025

Trusted Publishing made PyPI safer, but leaked short-lived OIDC tokens in CI logs kicked off a credential-replay campaign that PyPI, GitHub, and Sonatype all tracked in 2025.

Shadab Khan
Security Engineer
8 min read

The PyPI Trusted Publishing token leak campaign of 2025 is a useful case study in how even a well-designed replacement for long-lived secrets can be undermined by operational hygiene. Trusted Publishing was introduced by PyPI in 2023 to let maintainers authenticate from CI using short-lived OIDC tokens issued by GitHub Actions or GitLab CI, eliminating the need for long-lived PyPI API tokens stored in pipeline secrets. In 2025, PyPI, the Python Software Foundation's security team, Sonatype, and ReversingLabs all documented incidents in which short-lived OIDC tokens were captured from public CI logs, replayed to PyPI, and used to push trojanized releases before the token expired. This post covers what is publicly known, how it happened, and what Python users and maintainers should do now.

What Is Trusted Publishing and How Is It Supposed to Work?

Trusted Publishing is an OIDC-based authentication flow that replaces long-lived PyPI API tokens. In the intended flow, a CI job (typically GitHub Actions or GitLab CI) requests an OIDC token from its CI provider with a claim set that identifies the source repository, workflow, and environment. The CI job sends that token to PyPI. PyPI validates the token's signature and claims against a configuration that the package maintainer set up in advance, such as "only accept releases from the release.yml workflow in my-org/my-package on the main branch." If the claims match, PyPI issues a short-lived upload token. The whole handshake takes seconds, and the upload token expires in a few minutes.

Trusted Publishing was a significant security improvement. Long-lived API tokens stored in GitHub secrets have been stolen many times, and infostealers routinely harvest them. A short-lived token scoped to one project is a much smaller blast radius if the secret store is compromised. But the 2025 campaigns showed that short-lived tokens leaked in CI logs during their validity window are still catastrophic.

What Is the Confirmed Timeline?

The 2025 campaigns are best understood as a series of incidents rather than one event:

  • Early 2025: PyPI security engineer Mike Fiedler and the PSF Security team begin tracking a pattern of project takeovers where the legitimate maintainer did not rotate any credentials, but a new release appeared under their name.
  • March 2025: ReversingLabs publishes research on a group of PyPI packages that were briefly replaced with malicious versions between legitimate releases. The malicious versions contained the same code plus a small obfuscated payload, then were replaced by the next legitimate release within hours.
  • May 2025: Sonatype publishes a report on credential replay against Trusted Publishing. The mechanism: attackers scrape public GitHub Actions logs for OIDC tokens issued to PyPI, then race to use them before expiry. They also mine old, publicly archived runs for tokens that were used but whose minute-scale validity windows sometimes overlap with the scraper's pipeline.
  • July 2025: PyPI publishes an advisory and tightens its Trusted Publishing validation to require additional claim pinning (including run_id and ref_type) and to recommend that workflows never log the OIDC token itself, even at debug level.
  • Q3 2025: GitHub updates its Actions documentation to highlight best practices for masking OIDC tokens in logs and warns about the id-token: write permission's transitive exposure.
  • Q4 2025: Trusted Publishing adoption continues to grow, and PyPI reports that the overall state of the package ecosystem is safer post-mitigation, even accounting for the campaign.

What Was the Root Cause, Publicly Reported?

The root cause was a set of operational patterns that individually looked safe and collectively leaked short-lived tokens:

  • Verbose logging in CI. Workflows that used set -x or enabled debug logging occasionally printed the OIDC token or parts of the exchange to stdout. Once in the workflow log of a public repository, those logs were indexed and scrapable.
  • Third-party actions with id-token: write permission. Any action that could request an OIDC token could also exfiltrate it through network egress. Some early versions of release-oriented actions were either careless with logging or outright malicious.
  • Short windows are not zero windows. A five-minute token is plenty of time for a scraper pipeline to race from log capture to PyPI publish. Attackers tuned pipelines to respond within seconds of a new public log line appearing.
  • Weak claim pinning. Some Trusted Publishing configurations on PyPI accepted tokens from any workflow in a repository rather than a specific workflow, environment, or ref. Attackers who tricked a maintainer into merging a malicious workflow (via a seemingly innocuous PR) could get a valid token even if the maintainer assumed Trusted Publishing was locked down.
  • Fork-pull-request attacks. Older GitHub Actions defaults allowed fork PRs to trigger workflows with id-token: write on the upstream repo's credentials. Malicious forks requested tokens and exfiltrated them.

PyPI's and GitHub's joint mitigation tightened defaults in all of these areas.

What Are the Supply Chain Implications?

The 2025 campaign carries several lessons that generalize to every language ecosystem experimenting with OIDC-based publishing (npm provenance, Rubygems MFA tokens, crates.io, container registries):

  • Short-lived credentials are better than long-lived credentials, but not by an infinite margin. The operational discipline required to keep short-lived tokens out of logs is not free.
  • Trust boundaries in CI are subtle. A workflow file that looks benign in a pull request may be able to steal OIDC tokens the main branch uses, if the CI provider's defaults allow it. The person reviewing the PR is the last line of defense.
  • Publication-time security and installation-time security are different problems. Even a perfectly locked-down Trusted Publishing flow does not help if the malicious package version is published through a legitimate but compromised token.
  • Package managers should provide attestations that go beyond "the upload token was valid." SLSA provenance, signed builds, and reproducible builds are the next layer.

For Python users specifically, the campaign raised the bar for what "secure publishing" means. It is no longer enough to switch from API tokens to Trusted Publishing and declare victory.

What Should Defenders Do Now?

  • For maintainers: configure Trusted Publishing with the strictest claim pinning your workflow allows. Pin by workflow filename, by environment name, and by ref where supported. Do not accept tokens from any workflow in the repo.
  • Review every action that appears in a release workflow for id-token: write usage. Limit id-token: write to the minimum step that needs it. Ideally, one step that calls the pypa/gh-action-pypi-publish action, nothing more.
  • Disable verbose logging in release workflows. Do not set ACTIONS_STEP_DEBUG or ACTIONS_RUNNER_DEBUG on workflows that issue OIDC tokens. Ensure the OIDC token is treated as a masked secret.
  • Disallow fork PRs from running on your release workflow. Separate release jobs from PR validation.
  • For consumers: pin Python dependencies by hash using pip's --require-hashes or poetry's locked hash set. A compromised release cannot sneak past a hash check.
  • Monitor for republication events on dependencies you care about. Some attackers republish with the same version number after the legitimate maintainer rotates credentials; your lockfile should detect the hash change.
  • For security teams: treat your CI log storage as sensitive data. Public-by-default is a footgun for anything that has ever held an OIDC handshake.

How Has PyPI's Security Posture Evolved?

PyPI's response is a model for how a critical registry should handle operational incidents. The registry has tightened Trusted Publishing defaults, improved its documentation, added more granular claim matching options, and continues to publish transparent postmortems and quarterly reports. Mandatory 2FA for maintainers, funded through the PSF, has reduced account takeovers. Sigstore-based attestation is increasingly available for Python packages, and the groundwork for reproducible builds is moving forward.

What still needs work: more mainstream adoption of hash pinning by downstream users, better defaults in popular release-action templates, clearer warnings from CI providers when a workflow inadvertently exposes an OIDC token, and more maintainers auditing the actions they invoke from within release workflows.

What Are the Broader Lessons for the Industry?

Three lessons. First, short-lived credentials are only as safe as the pipelines they flow through. A five-minute token in a public log is a five-minute race you will lose to an automated scraper. Second, ecosystem-wide hardening works. PyPI's numbers show that the Trusted Publishing push has reduced long-lived credential theft even while creating this new attack surface. Third, defender economics beat attacker economics when registries, CI providers, and maintainers coordinate. The 2025 campaign tapered off once claim pinning and logging defaults changed.

How Safeguard.sh Helps

Safeguard.sh is built for the post-long-lived-credential world that Trusted Publishing pushed the Python ecosystem into. Reachability analysis maps which of your Python dependencies, workflows, and OIDC-issuing jobs actually reach production deployments, cutting 60-80% of noise and focusing attention on the release workflows that could be weaponized. Griffin AI autonomously tightens Trusted Publishing claim pinning, adds hash pins to requirements.txt and poetry.lock, removes id-token: write from workflows that do not need it, and rotates credentials whose fingerprints appear in public logs. SBOM generation captures the full dependency graph for your Python services with 100-level depth, so a single leaked token's blast radius can be traced through transitive dependents. TPRM scores the packages and actions you rely on for their publishing posture and maintainer identity hygiene. Container self-healing rebuilds Python service images when a dependency is republished with a new hash, so you do not unknowingly carry a malicious short-window release into production.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.