PyPI's new security indicators and unequal access to Trusted Publishing

I read the November 6 announcement of PyPI’s redesigned project pages with some concern about the implications for projects that don’t use one of PyPI’s approved Trusted Publishing providers.

I appreciate the work to improve package security and make provenance information more accessible. However, the new interface gives greater visibility to positive provenance indicators and introduces prominent warnings about missing or changed provenance. This raises a question:

Should PyPI present stronger security signals for publishing workflows that only a limited set of approved infrastructure providers can currently support?

I’m aware of the ongoing efforts to expand the pool of supported Trusted Publishers, including the specific request to support Codeberg. I also appreciate the legitimate security concerns involved in deciding which identity providers PyPI can trust. I don’t mean to minimize those concerns or suggest that PyPI is deliberately favoring commercial providers. Nevertheless, I had assumed that the limitations on supported providers would be substantially addressed before PyPI introduced more prominent security indicators that favor workflows using those providers.

For example, a project migrating from GitHub Actions to Codeberg could acquire a “Loss of Provenance” warning, not because its maintainers had adopted an inherently less trustworthy development process, but because PyPI doesn’t yet support their chosen infrastructure.

There are also legitimate reasons to use local publishing workflows rather than hosted CI/CD. As Glyph explains in this recent discussion, different projects have different threat models, and some maintainers deliberately prefer locally secured release credentials and explicit user confirmation.

I recognize that attestations provide meaningful, independently verifiable information about package origins. But they don’t establish that the code itself is safe, and their absence doesn’t necessarily imply a less secure publishing process.

I was particularly interested in Donald Stufft’s comments in March, explaining that Trusted Publishing and attestations had deliberately been presented neutrally in the UI to avoid creating community pressure to use particular platforms. That seems directly relevant to the recent changes.

I also appreciate William Woodruff’s recent clarification that PyPI has no intention of requiring attestations. However, the way PyPI presents security information could make attestations a de facto requirement, even if they remain officially optional.

My concern is that PyPI may unintentionally create a two-tier system in which projects using approved, predominantly commercial infrastructure receive stronger positive security signals, while projects committed to independent infrastructure or alternative release-security practices are placed at a disadvantage.

I’m curious whether these implications were considered when designing the new interface, particularly given the still-limited pool of supported providers.

Was there a change in thinking about the earlier decision to present attestations neutrally? And might there be ways to communicate the availability and limitations of provenance information without implying that projects lacking PyPI-supported attestations are necessarily less trustworthy?

6 Likes

First, to qualify my response: while I worked on the original Trusted Publishing and attestation designs/implementations, I wasn’t directly involved in the recent UI/presentation work. And my opinions are my own, not reflective of others.

Second, I want to clarify: Trusted Publishing is not the primary subject here. Trusted Publishing is an authentication mechanism, and it isn’t what the redesigned pages are trying to emphasize. They’re trying to emphasize PEP 740 attestations, which happen to be built on top of Trusted Publishing at the moment (because this was the easiest way to provide a default experience), but are not inherently tied to CI/CD providers like GitHub and GitLab. Attestations: email-based attestations · Issue #18007 · pypi/warehouse · GitHub is an idea for breaking that link, allowing people to produce email-based attestations that are not tied to CI/CD. I think it would be very valuable for the community help PyPI reach a design and decision point on that. It’s non-trivial, which is why it hasn’t moved forwards as much as I think everyone would like.

Third, I want to emphasize a few things from my response on that GitHub issue, which I appreciate you linking to. In particular:

  • PyPI wants to add more Trusted Publisher providers (= OIDC IdPs). This is a long-standing goal, and it’s impeded by two problems: the availability of review time from maintainers, and upstream IdP security and fidelity concerns that need to be resolve in the interest of users’ security. The latter in particular is a concern with Codeberg and other Forgejo-based forges, because they currently store IdP private key materials on disk. This would be, in my opinion, an unacceptable risk to PyPI’s users.
  • There is no foreseeable future in which installers (like pip and uv) can impose attestations as a requirement. It’s just not possible, on practical or philosophical grounds. The closest thing to a requirement will be giving users the ability to opt-in to attestation verification, which is also not something that’s immediately available (for various technical and social reasons).

Finally, something not in my responses on that issue: I think PyPI’s UI does do a good job of presenting the nuances here. In particular, there are a few states/state transitions that are relevant:

  1. A project has no attestations, and never has. This is a “neutral” state, meaning that there’s no positive or negative indicator on the project’s landing page associated with it. You can see my own zizmor project for an example of this (I’ve ironically not bothered to add attestations to it).
  2. A project has attestations. This is another “neutral” state. For example, sigstore itself on PyPI is attested, but there’s no big green badge indicating unique security, quality, or anything else. The closest thing to an indicator is a side marker noting that the latest release has “full provenance.”
  3. A project had attestations, but removed them in the latest release. This has an innocent root cause (a user switching forges, or publishing locally for whatever reason) as well as a potential malicious root cause (a package being compromised, without the attacker being able to perform an attestation). The latter is one of the main motivations for attestations: the operating theory is that many compromises involve developer credentials, and this is supported by credential harvest/exploitation cycles like this years Shai-Hulud campaign(s).
  4. A project has attestations, but in a partial state (meaning that the release has multiple files, but only a subset are attested). Like (3) this has both innocent and malicious origins: an innocent use is finishing a release after a remote release flow failed, while a malicious one is exploiting the fact that PyPI’s releases are currently open-ended to introduce wheels or sdists that are compromised post-facto.

The important thing with these states is that (1) and (2) are extremely common, while (3) and (4) are not very common (and should become less common over time). The UI thus weights the negative risk of (3) and (4) more seriously than the negative risk of (1), despite (1) - unattested releases - being logically as un-protected as (3). This is also intentional: indicating (1) would effectively be punishing users for never adopting attestations, which is not the goal.

This is a difficult tradeoff to make, but IMO it’s the correct one because, per above, it has no impact on installers (like pip and uv) themselves. It would be a different decision if it did, I think.

5 Likes

So if the cause is indeed innocent, how can the project return to the “neutral” state of not being marked as a potential risk? I’d assume that getting such a change would need explicit action on the part of the project, to ensure an attacker couldn’t also remove the flag, but I’d hope it’s possible so that projects don’t get marked negatively forever after a legitimate change.

4 Likes

Here’s another concrete example, I looked up Pillow, which I help co-maintain: pypi.org/project/pillow/#history.

We went from “No provenance” to “Full provenance” in October 2024. Since then, all have been “Full provenance” except one: 11.3.0 in July 2025 is flagged as “Incomplete provenance”.

PyPI says “20 of 106 release files were uploaded without provenance metadata”.

Looking more closely, 86 files were uploaded on 1st July, via GitHub Actions, and they link to the actual repo, commit, workflow and CI run.

However, 20 manylinux2014 and aarch64 wheels were uploaded on 3rd July, “without provenance metadata”, so not via GitHub Actions. PyPI says: “We recommend further investigation before installing files from this release.”

OK, in theory an attacker who doesn’t have access to our usual release workflow could have uploaded malicious wheels, so this is why further investigation is recommended.

What happened here? We’d bumped to cibuildwheel 3.0.0 for this release, which changed defaults so the release no longer included manylinux2014 wheels. We fixed it, which also brought back aarch64 wheels. We built them separately and manually uploaded them, hence no provenance, and a warning to check out why.

If the release had just gone out, and you saw the warning on PyPI, you would have found reassurance on our tracker that these were intentionally built separately and uploaded manually via twine:

(Aside: we now hardcode the expected number of wheels in the workflow, something I copied from Coverage.py and have also added to UltraJSON, so we don’t get any surprises from changes to cibuildwheel defaults, and can add/remove wheels intentionally.)

3 Likes

I just checked, and I believe the way the current implementation works is that it clears the “lost provenance” flag once 14 days after the last attested release has passed. (Source)

I didn’t work on this so I can’t infer the intent with that, but it seems likely that the idea was to balance interests: after 14 days without the package being deleted or otherwise quarantined, it’s likely that the change in attestation status was intentional. And subsequent releases will also not be marked as having changed, they’ll go back to the “neutral” state.

6 Likes

Thanks for the thoughtful response, William.

I appreciate the technical distinction between Trusted Publishing and attestations. However, that distinction doesn’t change the practical situation today. PyPI currently requires attestations to originate from approved Trusted Publisher identities. Until that restriction changes, the two are effectively coupled. My concern is that the new UI was introduced before this limitation was addressed.

I also understand the intention to treat projects without attestations as neutral, but I’m not convinced the UI actually communicates that neutrality.

Take your own zizmor project as an example. On the Release files tab, files are prominently labeled “Without provenance.” On the Release history tab, releases are also labeled “No provenance.” And on the Security tab, there’s an even more prominent “No provenance found” message and callout.

I recognize that these are factually accurate descriptions and that the accompanying text on the Security tab attempts to add some nuance (although mostly by deferring to much longer documents). But I suspect most users will interpret these repeated labels, particularly in a security context, as indications that something is missing or potentially wrong.

If the intention is to treat the absence of attestations as neutral, reserving negative signals for unexpected changes in provenance, then why do we emphasize that absence so prominently even when it represents a neutral state?

3 Likes

It’s very difficult, from a UI/UX perspective, to communicate the absence (or presence) of a state without people inferring a value judgement into it. Or in other words: the goal (of presenting a state as important without telling people what conclusions to draw) is a very difficult one.

With that in mind, I think PyPI’s UI decisions do a defensible job: they use value-neutral colors (grays and blues) rather than positive colors (reds and greens).

So that others can see without having to click through, this is what the security tab shows:

and the files tab:

If you have suggestions for how PyPI could communicate these states in an even more value-neutral way, I think everybody would be receptive to them. But I think it’s ultimately impossible for us to stop people (especially those engaging in motivated reasoning[1]) from reading value statements into anything PyPI does. This has been my personal experience over years of contributing to PyPI, writing standards to push the index side of the ecosystem forwards, etc.


  1. Not you, to be clear. ↩︎

2 Likes

How about just simply saying nothing at all about attestations in the neutral case? If that state is legitimately neutral then saying anything about attestations on that project’s page does not appear to be serving any purpose. The only effect is potentially to mislead users that there is a security issue with the project.

2 Likes

The landing page for a project that has no attestations has no indicator on it. You need to go into other tabs, where you’ll find other things that you can but shouldn’t necessarily infer value from, like a project’s wheel tags, distribution sizes, etc.

(I also don’t think there’s a clean way to show a negative here. One possibility is that PyPI could only show the “Security” tab for projects that have attestations. But then anybody could complain that the presence of that tab is itself a quality signal, despite that not being the intent. And it would also preclude using that tab for anything else.)

1 Like

I don’t know if it’s something that’s already been suggested, but I do wonder if a change of language would help at all. While “attestation” is the most technically correct term, unfortunately it’s been very strongly associated with one specific widely-disliked use case (forced “remote attestation” of the software a particular device is running). While the use of attestations in Python packaging has nothing whatsoever to do with that, I think the term has been sufficiently bombarded with that negative association that now people will just react to it automatically. So, even though it’s a less precise/correct phrasing, maybe documenting and talking about it in terms of signing and signatures would help? After all, people have been saying for years that they want language-specific package ecosystems to switch to “signed packages” for security :wink:

(and as someone who has been to keysigning parties and did PGP signing of uploaded artifacts in the past, I also want to add how incredibly happy I am that the Python packaging ecosystem is adopting a signing system that feels so much easier to use and better suited to the use case)

4 Likes

I’m personally not strongly tied to the term “attestation”! But it’s a hard one to change: it’s what the entire underlying stack (Sigstore, SLSA, in-toto, etc.) uses, and it’s also what other ecosystems (like npm) have adopted.

(This is unlike the situation with Trusted Publishing, where we picked the name intentionally to avoid confusing people with OIDC. But that also backfired, since people thought/think that “Trusted” refers to user trust rather than machine identity/server federation.)

Maybe “signed provenance”? But even this is imprecise: if we add support for email-based attestations, the attestation will be essentially a signature over a developer’s intent, not the package’s actual machine identity provenance. Naming is hard.

2 Likes

I don’t quite follow the objection to conditionally displaying the Security tab. I’m not suggesting removing it altogether, only omitting it when there’s no meaningful project-specific security information to display.

If a project has attestations, provenance warnings, or other relevant security information, then the tab serves a useful purpose. But if none of those conditions apply, what value is provided by a Security tab whose primary content is a prominent statement that no provenance was found?

I appreciate that even the presence or absence of a tab could be interpreted as a signal. But that seems a weaker concern than explicitly and repeatedly highlighting the absence of something that PyPI considers neutral.

Or… Alternatively, we could retain the Security tab for its other purposes, including general security guidance and malware reporting, but simply omit the absence-of-provenance message when that absence is considered neutral.

The Release files tab already provides a Details link for each distribution, showing checksums, upload information, and other metadata. That seems like an appropriate place to include the presence or absence of attestations for anyone who really wants that information in the neutral case.

Either approach would preserve access to meaningful security information without repeatedly highlighting an absence that PyPI explicitly considers neutral. Positive provenance information and genuine provenance warnings could continue to receive greater visibility.

1 Like

To make this concern more concrete, I host some of my projects on Codeberg, which PyPI doesn’t currently support for attestations. The new UI gives me an incentive to move my more public projects to GitHub and adopt an attested publishing workflow, not because I think that would necessarily make them more secure, but because I worry about the impression the current labels might give prospective users.

And this isn’t just hypothetical. Just yesterday, during a presentation of someone else’s project, I was asked about this “security notice” on PyPI which is what motivated me to start this thread.

This seems like exactly the kind of unintended pressure to use particular infrastructure providers that PyPI previously wanted to avoid.

2 Likes

At the end of the day, I think someone can just as easily draw an inference around the presence/absence of a security tab as the presence/absence of attestations. And this wouldn’t address the underlying state change problem, i.e. PyPI still wants to display something useful when a project does go from having attestations to not having them.

(This happens all the time with PyPI. For example, people draw inferences about how maintained a project is based on the last time it was updated. PyPI doesn’t tell people to do that, they just do based on a neutral presentation of metadata.)

I don’t follow this, sorry: if your projects are already on Codeberg, then PyPI’s presentation of them is purely neutral. The kind of security notice you’re talking about only happens in the other direction, and even expires after 14 days (which arguably could be made shorter, but serves to keep it from being a misleading permanent mark).

2 Likes

FWIW, as a publisher who’s firmly in the “I’m using whatever makes the most sense – please don’t push me to do otherwise” camp and was pretty uneasy seeing the discussions leading up the the PyPI UI changes, I think they’re pretty good. There’s nothing on the main page for a project without attestations. The “X no provenance” markers on the history page are all that’s there.

Those markers could maybe be done in a way to slightly less insinuate that something is missing but it’s no big deal.

3 Likes

I don’t follow this, sorry: if your projects are already on Codeberg, then PyPI’s presentation of them is purely neutral.

My argument is that the presentation is NOT neutral. I am not talking about the case where there is an unexpected change in provenance. I’m talking about the much more common case of a project that has never generated attestations. I claim that the presentation of that neutral case is unnecessarily alarming and unnecessary… and the resulting angst is no doubt unintentional.

I would caution people who are intimately familar with the technical nuances here to assume that their personal perceptions of the user experience is representative of the average user’s perception. Frankly, on first glance I didn’t see the problem either. It took a user asking me the question before I could see the problem and now I can’t unsee it.

2 Likes

While I’m not sure whether “No provenance” in itself can be seen as negative if you’re unfamiliar with the term, I do think that the X icon (despite using a neutral color) can be interpreted as such.

As someone who likes being able to see whether a release has an attestation, I did like some indication in the list when I first saw the new UI though. It made me realize I no longer have to click through to know, if the project has an attestation which I otherwise could think is simply not displayed until you click on a specific release. Now that I’m already familiar with the new UI though, I technically no longer need to see the “no attestation” information since I’ve since learnt that this information is presented when there is attestation to speak of. But it probably wouldnt be good UX to assume the user already knows this.

3 Likes

Do you think an information icon, similar to the one used on the security details tab, would be better? Or no icon at all in that case?

3 Likes

Personally I think ⓘ is considerably more neutral than ⊗, even without the additional signal of color.

4 Likes

There’s something I didn’t initially raise when suggesting Codeberg as a new trusted publisher, that has been bothering me since.

We (the people discussing Codeberg in the thread linked above) agree that Codeberg’s CI infrastructure is not mature enough to become a trusted publisher; I agree emphatically, which is why I’m working on FelineBuildServices (which uses VM isolation per-build and NixOS for base image management, so as to minimize fallout from compromised builds and human error, respectively). FBS isn’t mature enough either.

But how do we know that GitHub is? And, just as importantly, how would we know if GitHub’s infrastructure no longer satisfies the requirements? There’s no real transparency nor a way to evaluate it independently, as far as I know. How would you even begin to evaluate it proactively? As best as I can tell, currently the evaluation is reactive, which I personally don’t think is enough.

3 Likes