<?xml version='1.0' encoding='utf-8'?>
<?xml-stylesheet type="text/xsl" href="/sheet.xsl"?><rss version="2.0"><channel><title>ENOSUCHBLOG</title><description>Programming, philosophy, pedaling.</description><item><title>Introducing vulnbrocards.com</title><link>https://blog.yossarian.net/2026/08/31/Introducing-vulnbrocards-com</link><description>TL;DR: I’ve turned my April post on Brocards for vulnerability triage into a more permanent website. You can now link to each brocard directly, like this, without having to worry about link rot1. My blog’s URLs should be very stable, but a separate domain and site &amp;#8617;</description><ns0:encoded xmlns:ns0="http://purl.org/rss/1.0/modules/content/">&lt;body morss_own_score="1.4482758620689653" morss_score="29.07758620689655"&gt;
&lt;h1&gt;ENOSUCHBLOG&lt;/h1&gt;
&lt;h2&gt;&lt;em&gt;Programming, philosophy, pedaling.&lt;/em&gt;&lt;/h2&gt;
&lt;hr&gt;
&lt;h1&gt;
&lt;a href="https://blog.yossarian.net/2026/08/31/Introducing-vulnbrocards-com"&gt;Introducing vulnbrocards.com&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;
&lt;em&gt;Aug 31, 2026&lt;/em&gt;

       

    
      &lt;span&gt;
        Tags:
        
        
          &lt;a href="https://blog.yossarian.net/tags#oss"&gt;oss&lt;/a&gt;,
        
          &lt;a href="https://blog.yossarian.net/tags#security"&gt;security&lt;/a&gt;
&lt;/span&gt;
    

       

    
  &lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;TL;DR: I’ve turned my April post on
&lt;a href="https://blog.yossarian.net/2026/04/11/Brocards-for-vulnerability-triage"&gt;Brocards for vulnerability triage&lt;/a&gt;
into a more permanent website. You can now link to each brocard directly, &lt;a href="https://vulnbrocards.com/brocards/vb001-no-vuln-report-without-threat-model/"&gt;like this&lt;/a&gt;,
without having to worry about link rot&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/08/31/Introducing-vulnbrocards-com#fn:blog"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;This is a short announcement post for &lt;a href="https://vulnbrocards.com"&gt;vulnbrocards.com&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://vulnbrocards.com"&gt;&lt;img src="https://blog.yossarian.net/assets/vulnbrocards.com.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Several people have asked me for a more stable way to reference the brocards
in &lt;a href="https://blog.yossarian.net/2026/04/11/Brocards-for-vulnerability-triage"&gt;the original post&lt;/a&gt;, and this is my long-term solution. I hope you (dear reader)
find it useful!&lt;/p&gt;
&lt;hr&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;My blog’s URLs should be very stable, but a separate domain and site
     should be even more so. &lt;a href="https://blog.yossarian.net/2026/08/31/Introducing-vulnbrocards-com#fnref:blog"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;hr&gt;

&lt;a href="https://blog.yossarian.net/2026/08/13/python-packaging-council"&gt;Previously&lt;/a&gt;

&lt;/body&gt;
</ns0:encoded><pubDate>Mon, 31 Aug 2026 00:00:00 UTC</pubDate></item><item><title>Running for the Python Packaging Council</title><link>https://blog.yossarian.net/2026/08/13/python-packaging-council</link><description>TL;DR: I’m announcing my candidacy for the inaugural Python Packaging Council. I hope you’ll honor me with your vote, if you’re a voting member of the PSF, but regardless I have high hopes (and expectations) for the council.</description><ns0:encoded xmlns:ns0="http://purl.org/rss/1.0/modules/content/">&lt;body morss_own_score="2.764705882352941" morss_score="41.48692810457516"&gt;
&lt;h1&gt;ENOSUCHBLOG&lt;/h1&gt;
&lt;h2&gt;&lt;em&gt;Programming, philosophy, pedaling.&lt;/em&gt;&lt;/h2&gt;
&lt;hr&gt;
&lt;h1&gt;
&lt;a href="https://blog.yossarian.net/2026/08/13/python-packaging-council"&gt;Running for the Python Packaging Council&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;
&lt;em&gt;Aug 13, 2026&lt;/em&gt;

       

    
      &lt;span&gt;
        Tags:
        
        
          &lt;a href="https://blog.yossarian.net/tags#oss"&gt;oss&lt;/a&gt;,
        
          &lt;a href="https://blog.yossarian.net/tags#python"&gt;python&lt;/a&gt;
&lt;/span&gt;
    

       

    
  &lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;TL;DR: I’m announcing my candidacy for the inaugural &lt;a href="https://peps.python.org/pep-0772/"&gt;Python Packaging Council&lt;/a&gt;.
I hope you’ll honor me with your vote, if you’re a voting member of the PSF,
but regardless I have high hopes (and expectations) for the council.&lt;/p&gt;
&lt;p&gt;My nomination statement, as submitted to the PSF:&lt;/p&gt;
&lt;p&gt;My name is William Woodruff (&lt;code&gt;@woodruffw&lt;/code&gt; on GitHub and DPO). I’m employed by OpenAI as a member of the Astral team; I currently focus on uv’s security features.&lt;/p&gt;
&lt;p&gt;I’ve been involved in Python (and Python packaging specifically) for about a decade; I’m currently a member of the PyPA, one of PyPI’s maintainers, and an active contributor to Python packaging standards and modernization efforts. I’ve been privileged to be a core part of several large security and modernization efforts over the years, including building &lt;code&gt;pip-audit&lt;/code&gt;, PyPI’s implementations of MFA, API tokens, and Trusted Publishing, alongside PEP 740 (attestations), PEP 792 (project status markers), PEP 753 (project URL handling), and a handful of other things.&lt;/p&gt;
&lt;p&gt;My interests in being on the Python Packaging Council are twofold:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I want the PPC to be a truly normative body for Python packaging standards, i.e. I want its decisions to have binding force and not to be informal and limited in normative effect (like PyPA membership often is). PEP 772 defines this mandate, but the efficacy of the PPC &lt;em&gt;in practice&lt;/em&gt; will depend in a large part on how proactive the inaugural council is willing to be.&lt;/li&gt;
&lt;li&gt;I want the PPC to have &lt;em&gt;future-oriented, optimistic&lt;/em&gt; ideas about how to make Python packaging better. In other words I &lt;em&gt;don’t&lt;/em&gt; want it to take an incrementalist approach, particularly in its augural session.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In sum, I want the inaugural session of the PPC to &lt;strong&gt;establish a strong precedent&lt;/strong&gt; for the PPC having &lt;strong&gt;both&lt;/strong&gt; the political and technical authority to make large-scale changes to Python packaging.&lt;/p&gt;
&lt;p&gt;My concrete platform:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I will push for a modernization/overhaul of the Python packaging PEP process: a more streamlined and well-defined draft-to-PEP state machine, concrete assignments and timelines for responsible individuals, and an overhaul of the “living standard” process (which currently allows packaging.python.org to languish).&lt;/li&gt;
&lt;li&gt;I will push for the PPC to adopt a system of “good standing” for standard Python packaging tooling: Python packaging tooling that wishes to be considered “official” will need to remain in good standing, which will involve adopting relevant standards in a timely manner. This is a tall order, and will require us to carefully balance individual maintainers’ interests and time commitments with the larger community’s interests.&lt;/li&gt;
&lt;li&gt;I will advocate for the PPC to speedily approve and accelerate the adoption of a handful of next-generation standards, including:
    &lt;ul&gt;
&lt;li&gt;PEP 694 (Upload 2.0)&lt;/li&gt;
&lt;li&gt;Wheel variants/future wheelnext PEPs/JSON metadata&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;I will advocate for the PPC to research and advance additional potential next-generation standards, including:
    &lt;ul&gt;
&lt;li&gt;An improved archive format for Python packaging (replacing &lt;code&gt;.tar.gz&lt;/code&gt; and/or &lt;code&gt;.zip&lt;/code&gt;), as part of a larger effort to eliminate archive differentials and ambiguities by construction, instead of patching around them.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;I will advocate for the PPC to deprecate and remove legacy aspects of Python packaging, via the standards process. Some potential candidates:
    &lt;ul&gt;
&lt;li&gt;Deprecating and eventually forbidding the upload of older metadata versions (and/or legacy individual metadata fields).&lt;/li&gt;
&lt;li&gt;Deprecating “source archives” (versus sdists) as an installable artifact, as part of a larger initiative to improve the adoption of structured metadata in Python packaging processes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;

&lt;a href="https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints"&gt;Previously&lt;/a&gt;


&lt;a href="https://blog.yossarian.net/2026/08/31/Introducing-vulnbrocards-com"&gt;Newer&lt;/a&gt;

&lt;/body&gt;
</ns0:encoded><pubDate>Thu, 13 Aug 2026 00:00:00 UTC</pubDate></item><item><title>GitHub Actions needs OIDC audience constraints</title><link>https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints</link><description>TL;DR: GitHub Actions should allow end-users to express audience constraints, to make it harder for an attacker to pivot across services that use independent OIDC-bearing jobs. They could do this with relatively small syntax tweak, although the backend implications are probably nontrivial.</description><ns0:encoded xmlns:ns0="http://purl.org/rss/1.0/modules/content/">&lt;body morss_own_score="2.756991321118611" morss_score="98.06837932557917"&gt;
&lt;h1&gt;ENOSUCHBLOG&lt;/h1&gt;
&lt;h2&gt;&lt;em&gt;Programming, philosophy, pedaling.&lt;/em&gt;&lt;/h2&gt;
&lt;hr&gt;
&lt;h1&gt;
&lt;a href="https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints"&gt;GitHub Actions needs OIDC audience constraints&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;
&lt;em&gt;Aug 10, 2026&lt;/em&gt;

       

    
      &lt;span&gt;
        Tags:
        
        
          &lt;a href="https://blog.yossarian.net/tags#dear-github"&gt;dear-github&lt;/a&gt;,
        
          &lt;a href="https://blog.yossarian.net/tags#oss"&gt;oss&lt;/a&gt;,
        
          &lt;a href="https://blog.yossarian.net/tags#security"&gt;security&lt;/a&gt;
&lt;/span&gt;
    

       

    
  &lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: GitHub Actions should allow end-users to express &lt;em&gt;audience constraints&lt;/em&gt;, to
make it harder for an attacker to pivot across services that use independent OIDC-bearing jobs.
They could do this with relatively small syntax tweak, although the backend implications are
probably nontrivial.&lt;/p&gt;
&lt;p&gt;Like many CI/CD providers, GitHub Actions provides verifiable machine identities&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints#fn:ident"&gt;1&lt;/a&gt;&lt;/sup&gt;
via &lt;a href="https://openid.net/developers/how-connect-works/"&gt;OpenID Connect&lt;/a&gt; (OIDC).&lt;/p&gt;
&lt;p&gt;These are awesome for a lot of reasons, not least of which is that they allow
workflows running on GitHub Actions to federate with other (third-party) services
&lt;em&gt;without&lt;/em&gt; GitHub having to intermediate and pre-bless every interaction.&lt;/p&gt;
&lt;p&gt;This is the backbone of how both &lt;a href="https://docs.pypi.org/trusted-publishers/"&gt;Trusted Publishing&lt;/a&gt; and &lt;a href="https://sigstore.dev"&gt;Sigstore&lt;/a&gt; work:
an individual workflows on GitHub Actions presents its machine identity
(via an OIDC token) to an external service, which then authenticates and
for some purpose (uploading to PyPI or signing artifacts, respectively).&lt;/p&gt;
&lt;p&gt;Unfortunately, GitHub’s &lt;em&gt;mechanism&lt;/em&gt; for exposing OIDC tokens in workflows
contains a significant weakness, one that (in my opinion) will present an
increasingly serious security risk over time. This post is about that weakness.&lt;/p&gt;
&lt;h2&gt;CI/CD and OIDC&lt;a href="https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints#cicd-and-oidc"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At the core of all “OIDC in CI/CD” implementations is &lt;em&gt;some&lt;/em&gt; mechanism
that allows the workflow (pipeline definition, etc.) to request or
otherwise be pre-loaded with an OIDC identity.&lt;/p&gt;
&lt;p&gt;Here’s how that looks in GitLab CI/CD:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1
2
3
4
5
6
&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre&gt;&lt;span&gt;my-job&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
  &lt;span&gt;id_tokens&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
    &lt;span&gt;PYPI_ID_TOKEN&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
      &lt;span&gt;aud&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;pypi&lt;/span&gt;
  &lt;span&gt;script&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;|&lt;/span&gt;
    &lt;span&gt;do-something.sh --id-token "${PYPI_ID_TOKEN}"&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and here’s the equivalent in GitHub Actions:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1
2
3
4
5
6
7
8
9
10
11
&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre&gt;&lt;span&gt;my-job&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
  &lt;span&gt;runs-on&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;ubuntu-latest&lt;/span&gt;
  &lt;span&gt;permissions&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
    &lt;span&gt;id-token&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;write&lt;/span&gt;
  &lt;span&gt;steps&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
      &lt;span&gt;run&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;|&lt;/span&gt;
        &lt;span&gt;resp=$(curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \&lt;/span&gt;
          &lt;span&gt;"$ACTIONS_ID_TOKEN_REQUEST_URL&amp;amp;audience=pypi")&lt;/span&gt;
        &lt;span&gt;oidc_token=$(jq '.value' &amp;lt;&amp;lt;&amp;lt; "${resp}")&lt;/span&gt;

        &lt;span&gt;do-something.sh --token "${oidc_token}"&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The difference between these two is small but important: GitLab requires
the &lt;em&gt;OIDC audience&lt;/em&gt; (the &lt;code&gt;aud&lt;/code&gt;) to be &lt;strong&gt;declared up-front and statically&lt;/strong&gt;,
while GitHub requires the workflow to &lt;strong&gt;dynamically request&lt;/strong&gt; a token
with an audience selected at &lt;em&gt;runtime&lt;/em&gt; (the &lt;code&gt;audience&lt;/code&gt; parameter
in the HTTP request).&lt;/p&gt;
&lt;h2&gt;Why does this matter?&lt;a href="https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints#why-does-this-matter"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First, a very quick foray into OIDC.&lt;/p&gt;
&lt;p&gt;Under the hood, OIDC is (mostly) just &lt;a href="https://oauth.net/2/"&gt;OAuth 2.0&lt;/a&gt;, and OIDC identity
tokens are just &lt;a href="https://datatracker.ietf.org/doc/html/rfc7519"&gt;JSON Web Tokens&lt;/a&gt; (JWTs), with some additional&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints#fn:stringent"&gt;2&lt;/a&gt;&lt;/sup&gt; constrains
on the claims they should express.&lt;/p&gt;
&lt;p&gt;The most important claim in an OIDC ID token is arguably &lt;code&gt;sub&lt;/code&gt;, since it identifies
the principal (the “subject”)&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints#fn:sub"&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;However, the &lt;em&gt;second&lt;/em&gt; most important claim is &lt;code&gt;aud&lt;/code&gt;, for the &lt;em&gt;audience&lt;/em&gt;.
The audience is &lt;em&gt;critical&lt;/em&gt; because it &lt;strong&gt;constrains who honors the token&lt;/strong&gt;:
services that accept ID tokens should only do so when they recognize
the audience as matching theirs.&lt;/p&gt;
&lt;p&gt;In other words: the &lt;code&gt;aud&lt;/code&gt; claim prevents an ID token that’s &lt;em&gt;intentionally&lt;/em&gt;
been issued for a specific service from being stolen by the attacker
and &lt;em&gt;mis-applied&lt;/em&gt; to another service.&lt;/p&gt;
&lt;p&gt;This is intended as a defense-in-depth: even if an attacker manages to exfiltrate
an OIDC credential, they should not be able to &lt;em&gt;pivot&lt;/em&gt; to other services it.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.yossarian.net/assets/oidc-aud.jpg"&gt;&lt;/p&gt;
&lt;p&gt;It’s a flimsy defense but one that’s &lt;em&gt;generally&lt;/em&gt; effective&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints#fn:effective"&gt;4&lt;/a&gt;&lt;/sup&gt;, &lt;strong&gt;unless&lt;/strong&gt; you
give the attacker the ability to control the &lt;code&gt;aud&lt;/code&gt; claim as well.&lt;/p&gt;
&lt;p&gt;Unfortunately, that’s exactly what GitHub Actions enables&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints#fn:others"&gt;5&lt;/a&gt;&lt;/sup&gt;: &lt;code&gt;id-token: write&lt;/code&gt; gives the
job (or entire workflow) the ability to mint &lt;em&gt;any&lt;/em&gt; ID token it pleases, with &lt;em&gt;any&lt;/em&gt;
audience.&lt;/p&gt;
&lt;p&gt;This matters a great deal in a world (our world) where jobs that are given &lt;code&gt;id-token: write&lt;/code&gt;
&lt;em&gt;also&lt;/em&gt; run a lot of third-party code: any vulnerability (or malware) in that code
has the potential to ask for &lt;em&gt;new&lt;/em&gt; ID tokens for audiences that it isn’t &lt;em&gt;supposed&lt;/em&gt;
to have access to.&lt;/p&gt;
&lt;p&gt;We thought about this problem when designing Trusted Publishing, and came to the conclusion
that the machine identity that Trusted Publishing uses &lt;em&gt;must&lt;/em&gt; include the workflow name,
preventing an attacker from impersonating &lt;code&gt;pypi-publish.yml&lt;/code&gt; by inducing an ID token
from &lt;code&gt;aws-deploy.yml&lt;/code&gt; with &lt;code&gt;aud: pypi&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;However, this constraint is not suitable for all possible use cases: many integrations
want to use &lt;em&gt;just&lt;/em&gt; the &lt;code&gt;org/repo&lt;/code&gt; slug as a sufficient identity, meaning that all
workflows are effectively co-equal when issuing ID tokens.&lt;/p&gt;
&lt;h2&gt;What should GitHub do?&lt;a href="https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints#what-should-github-do"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Add constraints! Ideally, something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre&gt;&lt;span&gt;my-job&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
  &lt;span&gt;runs-on&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;ubuntu-latest&lt;/span&gt;
  &lt;span&gt;permissions&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;
    &lt;span&gt;id-token&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;&lt;span&gt;pypi&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The basic idea here is to &lt;em&gt;constrain&lt;/em&gt; what audiences the job
can request ID tokens for. In the example above, attempting to request
a token for &lt;code&gt;aud: sts.amazonaws.com&lt;/code&gt; would cause an error, preventing a job
that’s &lt;em&gt;intended&lt;/em&gt; only for publishing to PyPI from serving as a pivot to
AWS.&lt;/p&gt;
&lt;p&gt;There are, of course, some potential downsides to this. For example,
some services might (inadvisedly) require dynamic information
in their audience, meaning that a value can’t be statically
pre-declared. I think this is rare enough in practice to not be worth
blocking a security improvement and, when they &lt;em&gt;do&lt;/em&gt; occur, GitHub
could continue to allow the &lt;code&gt;id-token: write&lt;/code&gt; form as a (less secure!)
catch-all.&lt;/p&gt;
&lt;hr&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;More precisely, “workload identities.” But the distinction is not relevant here. &lt;a href="https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints#fnref:ident"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Not very stringent. &lt;a href="https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints#fnref:stringent"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In practice, the &lt;code&gt;sub&lt;/code&gt; claim is often a mess, since it’s typically an opaque string that uses &lt;code&gt;:&lt;/code&gt;
    or similar as a contentional delimiter. This causes all kinds of security bugs (e.g. when
    attacker-controlled components of the subject can &lt;em&gt;also&lt;/em&gt; contain the delimiter), which is why
    in practice Trusted Publishing and Sigstore both emphasize IdP-specific claims that represent
    each &lt;em&gt;part&lt;/em&gt; of the subject’s identity rather than a bespoke composite form. &lt;a href="https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints#fnref:sub"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Services can (and do) have bugs, like neglecting to check the audience or allowing
          it to vary with other potentially attacker-controlled claims. But we’re assuming
          services that &lt;em&gt;do&lt;/em&gt; properly check the audience here, like PyPI’s Trusted Publishing
          does. &lt;a href="https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints#fnref:effective"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Others appear to do the same thing, although I’m less familiar with other CI/CD providers.
       For example, BuildKite’s OIDC flow involves invoking
       &lt;code&gt;buildkite-agent oidc request-token --audence &amp;lt;aud&amp;gt;&lt;/code&gt;, which involves the same level of runtime
       control of the audience. &lt;a href="https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints#fnref:others"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;hr&gt;
&lt;span&gt;
  Discussions:
  
  &lt;a href="https://infosec.exchange/@yossarian/117071427417703584"&gt;Mastodon&lt;/a&gt;
&lt;a href="https://bsky.app/profile/yossarian.net/post/3msq73jol6m2u"&gt;Bluesky&lt;/a&gt;
&lt;a href="https://www.reddit.com/r/enosuchblog/comments/1vkkrbr/github_actions_needs_oidc_audience_constraints/"&gt;Reddit&lt;/a&gt;
&lt;/span&gt;
&lt;hr&gt;

&lt;a href="https://blog.yossarian.net/2026/07/16/README-not"&gt;Previously&lt;/a&gt;


&lt;a href="https://blog.yossarian.net/2026/08/13/python-packaging-council"&gt;Newer&lt;/a&gt;

&lt;/body&gt;
</ns0:encoded><pubDate>Mon, 10 Aug 2026 00:00:00 UTC</pubDate></item><item><title>README, not</title><link>https://blog.yossarian.net/2026/07/16/README-not</link><ns0:encoded xmlns:ns0="http://purl.org/rss/1.0/modules/content/">&lt;body morss_own_score="2.718045112781955" morss_score="44.06104028186408"&gt;
&lt;h1&gt;ENOSUCHBLOG&lt;/h1&gt;
&lt;h2&gt;&lt;em&gt;Programming, philosophy, pedaling.&lt;/em&gt;&lt;/h2&gt;
&lt;hr&gt;
&lt;h1&gt;
&lt;a href="https://blog.yossarian.net/2026/07/16/README-not"&gt;README, not&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;
&lt;em&gt;Jul 16, 2026&lt;/em&gt;

       

    
      &lt;span&gt;
        Tags:
        
        
          &lt;a href="https://blog.yossarian.net/tags#ai"&gt;ai&lt;/a&gt;,
        
          &lt;a href="https://blog.yossarian.net/tags#oss"&gt;oss&lt;/a&gt;
&lt;/span&gt;
    

       

    
  &lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img src="https://blog.yossarian.net/assets/borat-not.png"&gt;&lt;/p&gt;
&lt;p&gt;(Thanks to &lt;a href="https://github.com/facutuesca"&gt;Facundo Tuesca&lt;/a&gt; for the name inspiration).&lt;/p&gt;
&lt;p&gt;If you’re like me, you spend a lot of your working day (and a good chunk of your
personal time) reading code online. Increasingly, that means accidentally reading
a lot of “slop”&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/07/16/README-not#fn:slop"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Personally, slop isn’t annoying &lt;em&gt;per se&lt;/em&gt;&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/07/16/README-not#fn:codegen"&gt;2&lt;/a&gt;&lt;/sup&gt;: it’s okay for personal software&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/07/16/README-not#fn:personal"&gt;3&lt;/a&gt;&lt;/sup&gt;,
for example, to be slop. What makes slop annoying is the feeling of being
bait-and-switched: much like the written word, I want to be &lt;em&gt;informed&lt;/em&gt;&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/07/16/README-not#fn:informed"&gt;4&lt;/a&gt;&lt;/sup&gt;
before I spend my human attention on machine outputs.&lt;/p&gt;
&lt;p&gt;I’m a big believer in giving people a way to express honest intentions. For example,
I &lt;em&gt;do&lt;/em&gt; sometimes want to drop some slop on the Internet (to save for myself later,
or for others to reuse without reading), but I don’t want to mislead people about
the intent or effort behind it.&lt;/p&gt;
&lt;p&gt;So: what if we gave people a way to express their honest intentions with slop?
We use &lt;code&gt;README&lt;/code&gt; files to tell users where to start when reading a project;
I think we should have a &lt;code&gt;READMENOT&lt;/code&gt;&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/07/16/README-not#fn:naming"&gt;5&lt;/a&gt;&lt;/sup&gt; file that users (or their agents)
can add to their projects when they’re slopping it up. The presence of that
file would serve as an unambigous warning that the code within the project
is unsuitable for &lt;em&gt;unwitting&lt;/em&gt; human comprehension&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/07/16/README-not#fn:comprehension"&gt;6&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;READMENOT&lt;/code&gt; could contain anything, but it seems to me like a good default
would be a short human-friendly explanation of &lt;em&gt;why&lt;/em&gt; the project shouldn’t be read.
For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;pre&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre&gt;Warning!

You're reading a project that isn't intended for direct human consumption.
You may wish to use an LLM or another tool to interact automatically with
this project.
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;What is or isn’t “slop” in the context of programming is currently a matter
of energetic (and sometimes emotionally charged) debate. I personally draw the line
with either of two sufficient qualities: to me, a codebase is &lt;em&gt;slop&lt;/em&gt; if it either
(1) is developed &lt;em&gt;primarily&lt;/em&gt; without human supervision, or (2) reflects a &lt;em&gt;fundamental&lt;/em&gt;
lack of operator understanding. These are qualities sometimes occur at the same time,
but either suffices.&lt;/p&gt;
&lt;p&gt;It’s also worth noting that LLMs are getting better; 2026’s slop is not 2025’s slop.
That makes it hard to grant “slop” as a static qualifier; this post reflects reality in
July 2026. &lt;a href="https://blog.yossarian.net/2026/07/16/README-not#fnref:slop"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I personally think about slop like generated code: it’s not annoying
for generated code &lt;em&gt;to exist&lt;/em&gt;, but it is annoying to find yourself reading it
because it lacks the appropriate &lt;code&gt;@generated&lt;/code&gt; (or whatever) marker. &lt;a href="https://blog.yossarian.net/2026/07/16/README-not#fnref:codegen"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Meaning software that solves a personal problem, is bespoke, is single-use or
“disposable,” &amp;amp;c. &lt;a href="https://blog.yossarian.net/2026/07/16/README-not#fnref:personal"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I often choose to read machine-generated things. Being able to make
an &lt;em&gt;informed decision&lt;/em&gt; to do so is what matters to me. &lt;a href="https://blog.yossarian.net/2026/07/16/README-not#fnref:informed"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I don’t really care &lt;em&gt;what&lt;/em&gt; this file is named, or that it’s a file at all:
&lt;em&gt;any&lt;/em&gt; kind of consistent marker would suffice. But calling it &lt;code&gt;READMENOT&lt;/code&gt; is funny,
so that’s what we’re going with for this post. &lt;a href="https://blog.yossarian.net/2026/07/16/README-not#fnref:naming"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There are lots of good reasons to still read slop as a human, not least of
which is performing security research. The idea behind &lt;code&gt;READMENOT&lt;/code&gt; not to tell you
to &lt;em&gt;never&lt;/em&gt; read a codebase, only to inform you about you what might expect to see if you
&lt;em&gt;do&lt;/em&gt; decide to read it. &lt;a href="https://blog.yossarian.net/2026/07/16/README-not#fnref:comprehension"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;hr&gt;
&lt;span&gt;
  Discussions:
  
  &lt;a href="https://infosec.exchange/@yossarian/116929949470585620"&gt;Mastodon&lt;/a&gt;
&lt;a href="https://bsky.app/profile/yossarian.net/post/3mqreksj7lw25"&gt;Bluesky&lt;/a&gt;
&lt;a href="https://www.reddit.com/r/enosuchblog/comments/1uy3apv/readme_not/"&gt;Reddit&lt;/a&gt;
&lt;/span&gt;
&lt;hr&gt;

&lt;a href="https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing"&gt;Previously&lt;/a&gt;


&lt;a href="https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints"&gt;Newer&lt;/a&gt;

&lt;/body&gt;
</ns0:encoded><pubDate>Thu, 16 Jul 2026 00:00:00 UTC</pubDate></item><item><title>You shouldn’t trust Trusted Publishing</title><link>https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing</link><description>…because Trusted Publishing isn’t for you (or me) to trust! It’s for the machines.</description><ns0:encoded xmlns:ns0="http://purl.org/rss/1.0/modules/content/">&lt;body morss_own_score="2.7492202121023084" morss_score="104.28858278016169"&gt;
&lt;h1&gt;ENOSUCHBLOG&lt;/h1&gt;
&lt;h2&gt;&lt;em&gt;Programming, philosophy, pedaling.&lt;/em&gt;&lt;/h2&gt;
&lt;hr&gt;
&lt;h1&gt;
&lt;a href="https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing"&gt;You shouldn't trust Trusted Publishing&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;
&lt;em&gt;Jul 7, 2026&lt;/em&gt;

       

    
      &lt;span&gt;
        Tags:
        
        
          &lt;a href="https://blog.yossarian.net/tags#oss"&gt;oss&lt;/a&gt;,
        
          &lt;a href="https://blog.yossarian.net/tags#python"&gt;python&lt;/a&gt;,
        
          &lt;a href="https://blog.yossarian.net/tags#security"&gt;security&lt;/a&gt;
&lt;/span&gt;
    

       

    
  &lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;…because &lt;a href="https://docs.pypi.org/trusted-publishers/"&gt;Trusted Publishing&lt;/a&gt; isn’t for you (or me) to trust! It’s for the machines.&lt;/p&gt;
&lt;p&gt;Trusted Publishing is an authentication scheme that involves &lt;em&gt;machine-to-machine&lt;/em&gt; trust.
If you find yourself arguing against (or for!) Trusted Publishing on the basis that
&lt;em&gt;you&lt;/em&gt; (a human) can’t (or can) trust it then you’re making a category error, one that PyPI
is careful to help you &lt;strong&gt;not&lt;/strong&gt; make.&lt;/p&gt;
&lt;p&gt;I wrote about this indirectly &lt;a href="https://blog.yossarian.net/2024/11/18/Security-means-securing-people-where-they-are"&gt;about two years ago&lt;/a&gt;, but I left this observation implicit rather
than laying it out explicitly. This is an attempt to correct that mistake.&lt;/p&gt;
&lt;h2&gt;Quick recap&lt;a href="https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#quick-recap"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;“Trusted Publishing” is a term of art that &lt;a href="https://pypi.org"&gt;PyPI&lt;/a&gt; uses to describe a form of authentication
on top of OpenID Connect (&lt;a href="https://openid.net/developers/how-connect-works/"&gt;OIDC&lt;/a&gt;) federation. PyPI released it in 2023&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fn:dev"&gt;1&lt;/a&gt;&lt;/sup&gt;, and
it’s since been adopted widely by other packaging ecosystems (including &lt;a href="https://docs.npmjs.com/trusted-publishers"&gt;npm&lt;/a&gt;,
&lt;a href="https://guides.rubygems.org/trusted-publishing/"&gt;RubyGems&lt;/a&gt;, &lt;a href="https://crates.io/docs/trusted-publishing"&gt;crates.io&lt;/a&gt;, and &lt;a href="https://learn.microsoft.com/en-us/nuget/nuget-org/trusted-publishing"&gt;NuGet&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The basic observation behind Trusted Publishing is twofold:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Long-lived credentials (like index API tokens) are difficult to secure, and are
often over-scoped because users find it frustrating to determine the “right”
minimal scope and expiry. Consequently, the impact of a compromised or leaked
credential can be much broader than the specific system it was stolen from.&lt;/li&gt;
&lt;li&gt;Many users provision credentials solely for the purpose of putting them in a
CI/CD platform, which they then publish from. However, CI/CD platforms often
&lt;em&gt;also&lt;/em&gt; have identity mechanisms that allow a user to demonstrate control of a
specific machine identity&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fn:machine-identity"&gt;2&lt;/a&gt;&lt;/sup&gt; via OIDC.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Trusted Publishing combines these into an authentication scheme: the user
performs a one-time registration of their “Trusted Publisher” (their CI/CD machine
identity) with their package index. Then, whenever the CI/CD presents an identity
token, the package index verifies it and issues a corresponding, minimally scoped,
short-lived publishing credential.&lt;/p&gt;
&lt;p&gt;This can be hard to visualize; &lt;a href="https://sethmlarson.dev"&gt;Seth Larson&lt;/a&gt;’s diagram is instructive:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.yossarian.net/assets/trusted-publishers-flow.png"&gt;
&lt;em&gt;(Source: &lt;a href="https://repos.openssf.org/trusted-publishers-for-all-package-repositories.html"&gt;OpenSSF&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Broadly speaking, this approach to short-lived, self-scoping credentials has been a &lt;em&gt;huge success&lt;/em&gt;:
we’ve found that users&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fn:pypi"&gt;3&lt;/a&gt;&lt;/sup&gt; prefer not to manually manage credentials when they don’t have to,
and that large Open Source projects and companies alike both like the property of their publishing
being tied to their source identity rather than any individual project maintainer.&lt;/p&gt;
&lt;p&gt;Of course, Trusted Publishing is not perfect: it has a complicated data model&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fn:datamodel"&gt;4&lt;/a&gt;&lt;/sup&gt;,
requires bespoke treatment for each OIDC provider that an index chooses to support&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fn:support"&gt;5&lt;/a&gt;&lt;/sup&gt;,
and (of course) can still be compromised (since, at the end of the day, it’s still an
authentication scheme and there needs to be a credential &lt;em&gt;somewhere&lt;/em&gt; in the flow)&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fn:compromised"&gt;6&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Despite these qualifications, I think Trusted Publishing has been a net win for packaging ecosystems
that adopt it. As mentioned in &lt;a href="https://blog.yossarian.net/2024/11/18/Security-means-securing-people-where-they-are"&gt;the earlier post&lt;/a&gt;, security is a matter of identifying and protecting
the areas where your users congregate their trust (their “watering holes”), and Trusted Publishing
accomplishes that by reducing the number of long-lived, over-scoped credentials that users are exposed to.&lt;/p&gt;
&lt;h2&gt;What of trust?&lt;a href="https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#what-of-trust"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Thus far, everything I’ve written makes Trusted Publishing sound pretty good, if not a silver bullet.&lt;/p&gt;
&lt;p&gt;So, why the emphasis on &lt;strong&gt;not&lt;/strong&gt; trusting it?&lt;/p&gt;
&lt;p&gt;This is an important subtlety: Trusted Publishing &lt;strong&gt;is just an authentication method&lt;/strong&gt;.
The &lt;em&gt;only&lt;/em&gt; thing Trusted Publishing does is establish a trust relationship
between an external machine identity (like a CI/CD workflow) and a package identity on an index
&lt;strong&gt;for the purpose of authenticating an upload&lt;/strong&gt;;
it does &lt;strong&gt;not&lt;/strong&gt; tell a user that a package is safe to use, high quality, or &lt;em&gt;anything else&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;You can prove this to yourself easily: PyPI is a public index (by design), which means that anybody
can upload to it, &lt;em&gt;and&lt;/em&gt; anybody can use a Trusted Publisher to upload&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fn:can"&gt;7&lt;/a&gt;&lt;/sup&gt;. Because anybody
can use one, they can be used to upload &lt;em&gt;anything&lt;/em&gt;, including malware or vulnerable code.
They are &lt;em&gt;exactly&lt;/em&gt; like API tokens (PyPI’s other upload authentication method) in this regard.&lt;/p&gt;
&lt;p&gt;PyPI is &lt;em&gt;very careful&lt;/em&gt;&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fn:careful"&gt;8&lt;/a&gt;&lt;/sup&gt; to &lt;strong&gt;not&lt;/strong&gt; mislead users into believing that they can or should
trust a package based on its Trusted Publishing status. If you go to a project’s page on PyPI,
you’ll notice that there is no “magic green checkmark” signaling a project’s Trusted Publishing
status.&lt;/p&gt;
&lt;p&gt;Using &lt;a href="https://pypi.org/project/zizmor/"&gt;zizmor&lt;/a&gt; as an example:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.yossarian.net/assets/zizmor-pypi.png"&gt;&lt;/p&gt;
&lt;p&gt;Observe that the only “green checkmark” for user-controlled state on this page is for
links that PyPI can prove came from the same source as the package itself. These links,
too, are intentionally not described as “trusted”; as the &lt;a href="https://docs.pypi.org/project_metadata/#verified-details"&gt;PyPI docs explain&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A URL being verified only attests that the URL is under control of the PyPI package owner at the time of verification, and does not imply any additional safety about that URL or any other relationship to the project in question.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you &lt;em&gt;do&lt;/em&gt; want to see the Trusted Publishing status of a particular file on PyPI
you’ll need to dig for it. You’ll find it ignominiously rendered as a simple “Yes/No”
deep in the &lt;a href="https://pypi.org/project/zizmor/#zizmor-1.26.1.tar.gz"&gt;file details&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.yossarian.net/assets/zizmor-pypi-details.png"&gt;&lt;/p&gt;
&lt;p&gt;I say “ignominiously” because you can &lt;em&gt;see&lt;/em&gt; how little effort has been placed
into rendering the “file metadata” section, because it’s &lt;strong&gt;not important trust
information&lt;/strong&gt;. We don’t even bother to properly render the blob of JSON that comes
from the uploading client’s user agent!&lt;/p&gt;
&lt;p&gt;Here, too, you can see that no normative statement of trust is applied
to the Trusted Publishing status: there’s a boolean state, and
&lt;strong&gt;nothing whatsoever&lt;/strong&gt; to imply that a user can or should trust a project
more or less because of that state.&lt;/p&gt;
&lt;h2&gt;Summary&lt;a href="https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#summary"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Trusted Publishing is a mechanism for establishing trust between an external machine identity
(like a CI/CD workflow) and one or more projects on a package index/registry. The “trust”
in “Trusted Publishing” refers to that trust relationship, and not to anything else.&lt;/p&gt;
&lt;p&gt;It is not, and &lt;strong&gt;cannot be&lt;/strong&gt;, a signal for package trust or quality. You &lt;strong&gt;cannot&lt;/strong&gt;
use it to determine whether a package is safe or “good,” and PyPI &lt;em&gt;consciously
stymies&lt;/em&gt; attempts to misuse it for that purpose by not rendering it as a “green checkmark”
or anything else of the sort.&lt;/p&gt;
&lt;p&gt;Or as another framing: Trusted Publishing is &lt;strong&gt;just a form of authentication&lt;/strong&gt;.
It doesn’t tell you &lt;em&gt;anything&lt;/em&gt; other than that an upload was authenticated,
which all uploads to PyPI are.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.goodreads.com/quotes/361132-the-law-in-its-majestic-equality-forbids-rich-and-poor"&gt;With apologies to Anatole France&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Trusted Publishing, in its majestic equality, permits seasoned maintainer and script kiddy
alike to publish malware, to release slop for HN karma, and to obtain security advisories
for embarassing flaws.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Afterword&lt;a href="https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#afterword"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This post is about Trusted Publishing, &lt;em&gt;not&lt;/em&gt; &lt;a href="https://docs.pypi.org/attestations/"&gt;attestations&lt;/a&gt;. Attestations are a
technically distinct topic on PyPI that (currently) also use OIDC machine identities,
but are &lt;strong&gt;similarly not a signal of trust&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;To analogize: an attestation is effectively a signature on top of a machine identity,
but &lt;em&gt;anybody&lt;/em&gt; can upload to PyPI and therefore &lt;em&gt;anybody&lt;/em&gt; can sign with any machine
identity they control. The &lt;em&gt;presence&lt;/em&gt; of an attestation is not guaranteed by the
presence of a Trusted Publisher, and similarly does not imply any particular
end-user trust &lt;em&gt;until&lt;/em&gt; you &lt;em&gt;separately&lt;/em&gt; establish trust in that identity.&lt;/p&gt;
&lt;p&gt;This, too, is &lt;a href="https://docs.pypi.org/attestations/security-model/#trustworthiness"&gt;explicitly documented&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;To my recollection, we spent a good part of 2022 designing and implementing it. &lt;a href="https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fnref:dev"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The concept of a machine identity can be difficult to develop an intuition for,
in no small part because what “counts” as a sufficient identity varies wildly by
OIDC IdP.&lt;/p&gt;
&lt;p&gt;In the context of GitHub Actions (and Trusted Publishing specifically),
the “machine identity” is effectively a tuple of cryptographically verifiable claims
about the GitHub Actions workflow that issued the OIDC identity token.&lt;/p&gt;
&lt;p&gt;For example, the identity &lt;code&gt;(github, hello-world, publish.yml, pypi.org)&lt;/code&gt; might
correspond to a GitHub Actions workflow names &lt;code&gt;publish.yml&lt;/code&gt;, running underneath
the &lt;code&gt;github/hello-world&lt;/code&gt; repository, running with an environment named &lt;code&gt;pypi.org&lt;/code&gt;. &lt;a href="https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fnref:machine-identity"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;At least on PyPI. I don’t have direct evidence from other ecosystems. &lt;a href="https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fnref:pypi"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PyPI has “pending publishers” as a way to solve the chicken-and-egg problem
with nonexistent projects, but it’s not great: it complicates the data model significantly,
and users find it more confusing than “regular” Trusted Publishing. &lt;a href="https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fnref:datamodel"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This is primarily a consequence of OIDC’s own (in my opinion, excessive) flexibility:
outside of a common subset of claims, OIDC IdPs are free to put &lt;em&gt;anything&lt;/em&gt; in their claim
set.&lt;/p&gt;
&lt;p&gt;The consequence of this is that machine identities are not fungible across OIDC IdPs, and indices
typically need to handle each IdP’s unique “shape” on a case-by-case basis. This is one
of the reasons (but not the only reason) why PyPI adds new Trusted Publishing providers so slowly. &lt;a href="https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fnref:support"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;One of the themes of 2026 so far is attackers realizing that they &lt;em&gt;can&lt;/em&gt; exfiltrate a
Trusted Publishing credential (or its “seed” credential, the OIDC ID token), typically as a pivot after
compromising a CI/CD workflow.&lt;/p&gt;
&lt;p&gt;This is similar to what would happen if the workflow had a long-lived
credential provisioned against it, except that the Trusted Publishing credential doesn’t have the same
scoping and longevity risks.&lt;/p&gt;
&lt;p&gt;Still, it’s not ideal, and PyPI has taken steps to mitigate the (external)
problem of CI/CD compromise by e.g. refusing token exchanges when the machine identity corresponds
to a easily exploited trigger like &lt;code&gt;pull_request_target&lt;/code&gt;. &lt;a href="https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fnref:compromised"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Anybody can use a Trusted Publisher on PyPI, but they &lt;strong&gt;do not have to and will never have to&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It is &lt;strong&gt;simply infeasible as an engineering matter&lt;/strong&gt; (in &lt;em&gt;addition&lt;/em&gt; to being technically and
socially undesirable) to &lt;em&gt;force&lt;/em&gt; users into Trusted Publishing. It will &lt;strong&gt;always&lt;/strong&gt; be optional,
and &lt;strong&gt;cannot&lt;/strong&gt; be any other way. &lt;a href="https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fnref:can"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I believe most package indices are similarly careful, although I have less direct experience
with them. &lt;a href="https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fnref:careful"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;hr&gt;
&lt;span&gt;
  Discussions:
  
  &lt;a href="https://www.reddit.com/r/enosuchblog/comments/1upucn0/you_shouldnt_trust_trusted_publishing/"&gt;Reddit&lt;/a&gt;
&lt;a href="https://infosec.exchange/@yossarian/116878809694342665"&gt;Mastodon&lt;/a&gt;
&lt;a href="https://bsky.app/profile/yossarian.net/post/3mq2nt4tt2m24"&gt;Bluesky&lt;/a&gt;
&lt;/span&gt;
&lt;hr&gt;

&lt;a href="https://blog.yossarian.net/2026/04/29/Registering-my-dissatisfaction-with-GitHub"&gt;Previously&lt;/a&gt;


&lt;a href="https://blog.yossarian.net/2026/07/16/README-not"&gt;Newer&lt;/a&gt;

&lt;/body&gt;
</ns0:encoded><pubDate>Tue, 07 Jul 2026 00:00:00 UTC</pubDate></item></channel></rss>