<?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>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.7288135593220337" morss_score="44.07180872840416"&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;/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><item><title>Registering my dissatisfaction with GitHub</title><link>https://blog.yossarian.net/2026/04/29/Registering-my-dissatisfaction-with-GitHub</link><description>Mini-post.</description><ns0:encoded xmlns:ns0="http://purl.org/rss/1.0/modules/content/">&lt;body morss_own_score="2.518518518518518" morss_score="36.01165915699997"&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/04/29/Registering-my-dissatisfaction-with-GitHub"&gt;Registering my dissatisfaction with GitHub&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;
&lt;em&gt;Apr 29, 2026&lt;/em&gt;

       

    
      &lt;span&gt;
        Tags:
        
        
          &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;Mini-post.&lt;/p&gt;
&lt;p&gt;I read &lt;a href="https://mitchellh.com/writing/ghostty-leaving-github"&gt;Mitchell Hashimoto’s blog post&lt;/a&gt;
yesterday and it resonated with me; this part in particular:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Since then, I’ve opened GitHub every single day. Every day, multiple times per day,
for over 18 years. Over half my life. A handful of exceptions in there (I’d love to see the
data), but I can’t imagine more than a week per year.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I can’t claim quite the same seniority (I
&lt;a href="https://github.com/woodruffw"&gt;joined&lt;/a&gt; in 2012; my user ID is &lt;code&gt;3059210&lt;/code&gt;),
but I think I can fairly say that GitHub is both my favorite and the single most
important service I’ve interacted with, both as a hobbyist maintainer and as a professional
software engineer. I wouldn’t have the friends, connections, career, &amp;amp;c. that
I have today if it wasn’t for GitHub.&lt;/p&gt;
&lt;p&gt;Despite that, using GitHub has become a thoroughly &lt;em&gt;dissatisfying&lt;/em&gt; experience
over the last 18 months. I don’t know exactly &lt;em&gt;why&lt;/em&gt; that is&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/04/29/Registering-my-dissatisfaction-with-GitHub#fn:theories"&gt;1&lt;/a&gt;&lt;/sup&gt;,
but that is the brute reality. I now frequently interact with GitHub with a defensive,
rather than optimistic posture: I &lt;em&gt;assume&lt;/em&gt; that things will be slow, buggy,
or just plain broken, and my disappointment with the state of affairs
has been ground down from surprise (and a desire to help by reporting bugs)
to resignation and apathy.&lt;/p&gt;
&lt;p&gt;I hope that GitHub can turn things around, and that I get to use it for
years to come. But as of now, I’m dissatisfied with it and, for the first time
in over a decade, I am &lt;em&gt;seriously&lt;/em&gt; considering alternatives.&lt;/p&gt;
&lt;hr&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Theories abound, and are too well-trodden to be worth repeating in detail.
         In brief: a loss of pride-in-craft (stemming from Microsoft’s acquisition),
         deprioritization of feature development in the core product, an ill-advised React
         frontend rewrite, talent loss (voluntary and involuntary), a surge of AI-driven traffic,
         &amp;amp;c, &amp;amp;c. I have no idea which (if any) of these are to blame. &lt;a href="https://blog.yossarian.net/2026/04/29/Registering-my-dissatisfaction-with-GitHub#fnref:theories"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;hr&gt;

&lt;a href="https://blog.yossarian.net/2026/04/11/Brocards-for-vulnerability-triage"&gt;Previously&lt;/a&gt;


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

&lt;/body&gt;
</ns0:encoded><pubDate>Wed, 29 Apr 2026 00:00:00 UTC</pubDate></item><item><title>Brocards for vulnerability triage</title><link>https://blog.yossarian.net/2026/04/11/Brocards-for-vulnerability-triage</link><description>I spend some of my hobby time doing vulnerability triage on open source projects. As part of that, I see (and filter through) a lot of nonsense1. Spam, “beg bounty” submissions, and increasingly zero-effort LLM submissions. &amp;#8617;</description><ns0:encoded xmlns:ns0="http://purl.org/rss/1.0/modules/content/">&lt;body morss_own_score="2.667452830188679" morss_score="100.56608058906077"&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/04/11/Brocards-for-vulnerability-triage"&gt;Brocards for vulnerability triage&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;
&lt;em&gt;Apr 11, 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;I spend some of my hobby time doing vulnerability triage on open source projects.
As part of that, I see (and filter through) a lot of nonsense&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/04/11/Brocards-for-vulnerability-triage#fn:nonsense"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Nonsense is not unique to vulnerability triage: lawyers deal with it
too. To cope with it in the legal world, they use &lt;a href="https://en.wikipedia.org/wiki/Brocard_(law)"&gt;brocards&lt;/a&gt; —
concise aphorisms that capture the essence of a legal principle. Any given
brocard is not &lt;em&gt;universally&lt;/em&gt; true&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/04/11/Brocards-for-vulnerability-triage#fn:law"&gt;2&lt;/a&gt;&lt;/sup&gt;, but provides a standard by which a claim
can quickly be evaluated for legitimacy.&lt;/p&gt;
&lt;p&gt;Vulnerability triage has its own brocards, but I couldn’t find a comprehensive
list of them anywhere. This is my attempt to compile such a list.&lt;/p&gt;
&lt;h2&gt;No vulnerability report without a threat model&lt;a href="https://blog.yossarian.net/2026/04/11/Brocards-for-vulnerability-triage#no-vuln-without-threat-model"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://alexgaynor.net"&gt;Alex Gaynor&lt;/a&gt; explains this one well in
&lt;a href="https://alexgaynor.net/2025/oct/20/motion-to-dismiss/"&gt;Motion to Dismiss for Failure to State a Vulnerability&lt;/a&gt;: a vulnerability report
can be safely dismissed if it lacks a threat model, or if the threat model
presented is incoherent.&lt;/p&gt;
&lt;p&gt;Examples include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A report for a Python API that raises an exception in some undocumented
or surprising cases&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/04/11/Brocards-for-vulnerability-triage#fn:exceptions"&gt;3&lt;/a&gt;&lt;/sup&gt;, but doesn’t explain how an attacker could exploit that
behavior to cause harm.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A report for a hang or stall in a local developer tool. Hangs are undesirable behavior,
but the opportunity for harm from one is negligible in a developer tooling context:
the developer can always just kill the process.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;No exploit from the heavens&lt;a href="https://blog.yossarian.net/2026/04/11/Brocards-for-vulnerability-triage#no-exploit-from-heavens"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Closely related are vulnerability reports that describe a severe end state,
but only under attacker capability assumptions that are
&lt;em&gt;more powerful than the vulnerability itself&lt;/em&gt;. In effect, in order to mount
an attack that exploits the vulnerability, the attacker would &lt;em&gt;already&lt;/em&gt;
need to have an equal or more powerful capability than the vulnerability itself
provides.&lt;/p&gt;
&lt;p&gt;Examples include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A report for content manipulation on a web service, where the manipulation
can only occur if the attacker is an active &lt;a href="https://en.wikipedia.org/wiki/Man-in-the-middle_attack"&gt;meddler in the middle&lt;/a&gt;.
No vulnerability exists here, because an active MiTM could send entirely
arbitrary content, and would not have to limit themselves to manipulating
pre-existing content.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A report for code execution via memory corruption in CPython, where the
memory corruption occurs by directly manipulating CPython’s object internals
at runtime (via ctypes, for example). No vulnerability exists here, because
the attacker is &lt;em&gt;already&lt;/em&gt; running arbitrary code to perform the corruption.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://devblogs.microsoft.com/oldnewthing/20060508-22/?p=31283"&gt;Raymond Chen has 2006 post that covers the same concept&lt;/a&gt;. Thanks to
&lt;a href="https://ldpreload.com"&gt;Geoffrey Thomas&lt;/a&gt; for sharing that with me!&lt;/p&gt;
&lt;h2&gt;No vulnerability outside of usage&lt;a href="https://blog.yossarian.net/2026/04/11/Brocards-for-vulnerability-triage#no-vuln-outside-usage"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A vulnerability report can be safely dismissed if it describes a behavior
that &lt;em&gt;could&lt;/em&gt; occur, but does not in fact occur in actual usage of the software.&lt;/p&gt;
&lt;p&gt;Examples include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A report for a vulnerability in a private API within a library,
where the only (private) usage of that API is not vulnerable.&lt;/p&gt;
&lt;p&gt;For example, a C codebase might have a function that takes a &lt;code&gt;char *&lt;/code&gt; and
exhibits a buffer overflow with strings longer than 100 bytes,
but a codebase where all calls to that function are statically assertable
to not exceed that size is not vulnerable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Similarly, a report for a vulnerability in an API (public &lt;em&gt;or&lt;/em&gt; private),
where the vulnerability can only occur by violating an invariant that
the programmer is responsible for maintaining.&lt;/p&gt;
&lt;p&gt;For example, an API might have a precondition that an input string is valid
UTF-8, and an input that violates this precondition may cause an uncontrolled
program abort. However, a fuzzer that discovers this behavior has not
found a vulnerability, because in a real program the programmer is responsible
for ensuring that the “building blocks” of the API are composed together.&lt;/p&gt;
&lt;p&gt;It’s worth noting some nuance here: because the programmer is responsible for
maintaining the invariant, there &lt;em&gt;is&lt;/em&gt; a potentially legitimate vulnerability
when &lt;em&gt;usage&lt;/em&gt; of the API violates the invariant. By analogy:
&lt;code&gt;free(3)&lt;/code&gt; is not considered vulnerable to a double free, but a program that calls &lt;code&gt;free(3)&lt;/code&gt; on
an already freed pointer is considered vulnerable to a double free.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A re-report of a upstream’s vulnerability, where the upstream’s’s vulnerable
behavior is not reachable in the downstream.&lt;/p&gt;
&lt;p&gt;For example, CPython is shipped with a build of OpenSSL, and OpenSSL
&lt;a href="https://www.openssl.org/news/vulnerabilities.html"&gt;regularly has security advisories&lt;/a&gt;.
However, CPython’s exposure to OpenSSL is mostly limited to SSL/TLS and a subset
of the X.509 APIs, and therefore vulnerabilities outside of these surfaces
do not constitute a reasonable re-report to CPython.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;No vulnerability from standard behavior&lt;a href="https://blog.yossarian.net/2026/04/11/Brocards-for-vulnerability-triage#no-vuln-from-standard-behavior"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Perhaps my most controversial (and personal) brocard: a vulnerability report can
be safely dismissed if the behavior described is a direct consequence of the
software’s &lt;em&gt;correct&lt;/em&gt; adherence to a standard or specification. In these instances
the vulnerability (if one exists) is present within the standard itself,
and not the implementation.&lt;/p&gt;
&lt;p&gt;Examples include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Behavior stemming from “robustness” requirements in standards. Many RFCs
and similar standards inadvisably follow the (poorly named)
&lt;a href="https://en.wikipedia.org/wiki/Robustness_principle"&gt;robustness principle&lt;/a&gt;, and allow interactions that are not well-defined
(often by allowing the implementer to make a judgement call about
the &lt;em&gt;intended&lt;/em&gt; semantics of the interaction). For example, &lt;a href="https://datatracker.ietf.org/doc/html/rfc7230"&gt;RFC 7230&lt;/a&gt;
has this under “Message Parsing Robustness” (ss. 3.5):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In the interest of robustness, a server that is expecting to receive
 and parse a request-line SHOULD ignore at least one empty line (CRLF)
 received prior to the request-line.&lt;/p&gt;
&lt;p&gt;Although the line terminator for the start-line and header fields is
 the sequence CRLF, a recipient MAY recognize a single LF as a line
 terminator and ignore any preceding CR.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Behavior stemming from cryptographic requirements that are insecure in
isolation but secure by construction. The “classic”
example of this is (typically automated) reports of MD5 usage, where that
usage is solely in constructions where MD5 is not actually broken
(i.e. HMAC-MD5). There’s a strong argument to be made that a &lt;em&gt;better&lt;/em&gt;
hash function should be used where permitted, but the presence of MD5
in an HMAC construction is not itself a vulnerability.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The nuance with this is that an implementation that &lt;em&gt;chooses&lt;/em&gt; to be more
strict than the standard requires &lt;em&gt;should&lt;/em&gt; be considered vulnerable if the
intended strictness is violated.&lt;/p&gt;
&lt;h2&gt;No vulnerability from documented behavior&lt;a href="https://blog.yossarian.net/2026/04/11/Brocards-for-vulnerability-triage#no-vuln-from-documented-behavior"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;(Many thanks to &lt;a href="https://github.com/hugovk"&gt;Hugo van Kemenade&lt;/a&gt; for this one!)&lt;/p&gt;
&lt;p&gt;Similar to the above: a vulnerability report can be safely dismissed if the behavior
is documented to occur, &lt;em&gt;particularly&lt;/em&gt; when the documentation explicitly
describes the security implications of the behavior or specific
contexts in which the software is unsafe to use.&lt;/p&gt;
&lt;p&gt;Examples include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Python’s &lt;a href="https://docs.python.org/3/library/http.server.html"&gt;&lt;code&gt;http.server&lt;/code&gt;&lt;/a&gt; is explicitly documented as not suitable
for production use, as it only implements &lt;a href="https://docs.python.org/3/library/http.server.html#http-server-security"&gt;basic security checks&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Python’s &lt;a href="https://docs.python.org/3/library/pickle.html"&gt;&lt;code&gt;pickle&lt;/code&gt;&lt;/a&gt; is explicitly documented as not secure, full stop.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Like with the previous brocard, the nuance here is that a downstream &lt;em&gt;usage&lt;/em&gt;
that violates the documented guidelines for use may be considered vulnerable.
In other words: a report against &lt;code&gt;pickle&lt;/code&gt; itself (for e.g. enabling code execution)
may be safely dismissed, but a report against a downstream usage of &lt;code&gt;pickle&lt;/code&gt;
that ignores the documented warnings may be considered valid.&lt;/p&gt;
&lt;h2&gt;No cure worse than the disease&lt;a href="https://blog.yossarian.net/2026/04/11/Brocards-for-vulnerability-triage#no-cure-worse-than-disease"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The maintainer should reject (or contest) vulnerability reports whose consequences are
worse than the consequences of the vulnerability itself.&lt;/p&gt;
&lt;p&gt;The classic example of this is &lt;a href="https://blog.yossarian.net/2022/12/28/ReDoS-vulnerabilities-and-misaligned-incentives"&gt;ReDoS&lt;/a&gt; “vulnerabilities,” particularly in contexts
where the impact of the “denial of service” is negligible&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2026/04/11/Brocards-for-vulnerability-triage#fn:negligible"&gt;4&lt;/a&gt;&lt;/sup&gt;. These
reports typically involve nontrivial amounts of maintainer time and effort to
triage, followed by nontrivial amounts of &lt;em&gt;downstream&lt;/em&gt; time and effort to
remediate, effectively resulting in a denial of service on the community itself.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/advisories/GHSA-5239-wwwm-4pmq"&gt;CVE-2026-4539&lt;/a&gt; is a recent case of this: an anonymous reporter filed a CVE
against &lt;a href="https://github.com/pygments/pygments"&gt;pygments&lt;/a&gt; with &lt;a href="https://vuldb.com/"&gt;VulDB&lt;/a&gt;, seemingly bypassing any maintainer or community review.
This report was not accompanied by a fixed version (because it’s junk, and
ignores &lt;a href="https://pygments.org/docs/security/"&gt;pygments’ own security policy&lt;/a&gt;),
but lit up tens of thousands of downstream dependencies with a “medium”
severity vulnerability, &lt;a href="https://github.com/pygments/pygments/issues/3058"&gt;causing significant disruption&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The status quo in 2026 is that the CVE ecosystem unreasonably places
the onus on maintainers to contest this kind of spam when adversarial reporters
bypass them entirely.&lt;/p&gt;
&lt;h2&gt;The report is neither necessary nor sufficient&lt;a href="https://blog.yossarian.net/2026/04/11/Brocards-for-vulnerability-triage#report-neither-necessary-nor-sufficient"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The presence of a vulnerability report (and a CVE or other identifier for
that report) is neither necessary nor sufficient for a vulnerability to exist.&lt;/p&gt;
&lt;p&gt;This cuts both ways: many (perhaps the majority of) vulnerabilities are never
“formally” reported, and many formal reports do not actually describe
meaningful vulnerabilities (per above). Consequently, no &lt;em&gt;unvalidated&lt;/em&gt; assumption should
ever be made about the relationship between the presence of a report and the
presence of a vulnerability.&lt;/p&gt;
&lt;p&gt;This is another unfortunate status quo, one that stems from (seemingly
intentional) &lt;a href="https://blog.yossarian.net/2024/03/20/More-thoughts-on-vulnerabilities-and-misaligned-incentives"&gt;strategic ambiguity&lt;/a&gt; in the vulnerability reporting ecosystem:
partners like MITRE benefit simultaneously from being &lt;em&gt;perceived&lt;/em&gt; as a high-quality
source of vulnerability information, while also being able to disclaim any responsibility
for communicating anything other than a stable identifier for a &lt;em&gt;claim&lt;/em&gt; of vulnerability.&lt;/p&gt;
&lt;hr&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Spam, “beg bounty” submissions, and increasingly zero-effort LLM submissions. &lt;a href="https://blog.yossarian.net/2026/04/11/Brocards-for-vulnerability-triage#fnref:nonsense"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Brocards are also not &lt;em&gt;themselves&lt;/em&gt; law. &lt;a href="https://blog.yossarian.net/2026/04/11/Brocards-for-vulnerability-triage#fnref:law"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Vulnerability reports for libraries/components that raise exceptions
           in managed libraries are very common, and often belie a fundamental
           misunderstanding of the language’s execution contract. In Python,
           for example, the presumed execution contract is that an exception
           can occur at any point, and that a system should always be prepared
           to handle an exception. This doesn’t mean that every single block
           of code needs a &lt;code&gt;try..except&lt;/code&gt;, but that all exceptions &lt;em&gt;do&lt;/em&gt;
           get handled eventually, and therefore vulnerabilities only
           emerge when an exception is &lt;em&gt;mishandled&lt;/em&gt;. &lt;a href="https://blog.yossarian.net/2026/04/11/Brocards-for-vulnerability-triage#fnref:exceptions"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Two common reasons for the negligibility of ReDoS: (1) the
           only impact is to local workflows where the “denial” is trivially
           cancellable (such as a developer tool running in a terminal),
           and (2) the vulnerable behavior is only exhibited on arbitrary
           inputs that would otherwise pose a denial of service “risk”. &lt;a href="https://blog.yossarian.net/2026/04/11/Brocards-for-vulnerability-triage#fnref:negligible"&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/1sir2mf/brocards_for_vulnerability_triage/"&gt;Reddit&lt;/a&gt;
&lt;a href="https://infosec.exchange/@yossarian/116387519824893265"&gt;Mastodon&lt;/a&gt;
&lt;a href="https://bsky.app/profile/yossarian.net/post/3mjai6eav4527"&gt;Bluesky&lt;/a&gt;
&lt;/span&gt;
&lt;hr&gt;

&lt;a href="https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb"&gt;Previously&lt;/a&gt;


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

&lt;/body&gt;
</ns0:encoded><pubDate>Sat, 11 Apr 2026 00:00:00 UTC</pubDate></item><item><title>Some flexibility with Go’s sumdb</title><link>https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb</link><description>I noticed this a year or two ago, but forgot to write it up back then.</description><ns0:encoded xmlns:ns0="http://purl.org/rss/1.0/modules/content/">&lt;body morss_own_score="2.794912559618442" morss_score="131.56319682498543"&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/2025/12/29/Some-flexibility-with-Go-s-sumdb"&gt;Some flexibility with Go's sumdb&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;
&lt;em&gt;Dec 29, 2025&lt;/em&gt;

       

    
      &lt;span&gt;
        Tags:
        
        
          &lt;a href="https://blog.yossarian.net/tags#cryptography"&gt;cryptography&lt;/a&gt;,
        
          &lt;a href="https://blog.yossarian.net/tags#go"&gt;go&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;I noticed this a year or two ago, but forgot to write it up back then.&lt;/p&gt;
&lt;p&gt;I don’t think it’s particularly serious or important, but it’s an interesting
demonstration of how flexibility in identity can make
monitoring in transparency schemes nontrivial, as well as conflict with
user intuitions around which identities are equivalent.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: Go’s sumdb log entries are unique per module version, but there
are typically multiple valid case forms for the module &lt;em&gt;path&lt;/em&gt; that point to the
same module version &lt;em&gt;contents&lt;/em&gt;. These forms are &lt;strong&gt;not confusable&lt;/strong&gt; at import time,
but they complicate the monitoring story and could form the basis of a
typosquatting-like attack. Because the “typo” is just a case variation, it
looks less suspicious than a normal typo.&lt;/p&gt;
&lt;h2&gt;Background&lt;a href="https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb#background"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Go’s packaging design includes the &lt;a href="https://proxy.golang.org"&gt;Go module proxy&lt;/a&gt;, which is effectively
a caching proxy for Go modules. This serves (at least) two main purposes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;It makes Go packaging more reliable and resilient: &lt;code&gt;src.example.com/some/module@v1.2.3&lt;/code&gt;
will continue to work even if &lt;code&gt;src.example.com&lt;/code&gt; goes down, so long
as &lt;em&gt;someone&lt;/em&gt; has previously resolved that module and version through the proxy.
This also has the knock-on effect of making Go’s module resolution fast.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It provides a degree of integrity against unreliable, malicious, or compromised&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb#fn:tag"&gt;1&lt;/a&gt;&lt;/sup&gt;
upstreams: the proxy’s copy of the module is can’t be mutated &lt;em&gt;post facto&lt;/em&gt;
by the origin, meaning that downstreams that haven’t previously locked
their resolution still receive a version that’s consistent.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This second property is really useful, but still requires a degree of trust:
if the proxy &lt;em&gt;itself&lt;/em&gt; were compromised, it could serve malicious modules
to not-yet-locked downstreams. This would be easy for an attacker
to do in a precise manner (i.e. target specific victims) as well as difficult
to detect, since independent downstrams have no easy way of gossiping
amongst themselves about the proxy’s claimed responses.&lt;/p&gt;
&lt;h2&gt;Transparency&lt;a href="https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb#transparency"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In most ecosystems, the situation above (where the index is &lt;em&gt;de jure&lt;/em&gt;
immutable, but not easily &lt;em&gt;verifiably&lt;/em&gt; so) is the norm.&lt;/p&gt;
&lt;p&gt;However, Go went a step further and introduced the Go &lt;a href="https://go.googlesource.com/proposal/+/master/design/25530-sumdb.md"&gt;checksum database&lt;/a&gt;,
or “sumdb.” Go’s sumdb is a transparency log: an immutable, append-only
datastructure that yields &lt;em&gt;cryptographic proofs of inclusion&lt;/em&gt; for data entered
into it. Russ Cox has a nice &lt;a href="https://research.swtch.com/tlog"&gt;explainer on Merkle Trees and transparency log design&lt;/a&gt;;
it’s also the same basic technology behind &lt;a href="https://certificate.transparency.dev/"&gt;Certificate Transparency&lt;/a&gt; and
&lt;a href="https://www.sigstore.dev"&gt;Sigstore&lt;/a&gt;, albeit with different properties in each case&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb#fn:claimantmodel"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;With this database, Go’s module proxy becomes &lt;em&gt;verifiably monitorable and
auditable&lt;/em&gt;: the proxy is responsible for obtaining a &lt;em&gt;log entry&lt;/em&gt; prior to
serving a new module version, and clients can efficiently verify that the entry:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Exists in the log (i.e., the entry is not serving content that it hasn’t
committed to).&lt;/li&gt;
&lt;li&gt;Matches the claimed module content (i.e., the entry is not a
&lt;em&gt;valid but unrelated&lt;/em&gt; log entry).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In effect, this forces our would-be attacker into the open: they’re still
able to upload whatever malicious code they please&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb#fn:design"&gt;3&lt;/a&gt;&lt;/sup&gt;, but they must &lt;em&gt;commit&lt;/em&gt;
to doing so in a globally visible manner.&lt;/p&gt;
&lt;h2&gt;Monitoring&lt;a href="https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb#monitoring"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Transparency &lt;em&gt;itself&lt;/em&gt; is a useful technique for the game-theoretic reason
mentioned above: many attackers want to operate surreptitously and without
public evidence, and transparency logs prevent that.&lt;/p&gt;
&lt;p&gt;However, the mere presence of a transparency log does not foreclose on all
possible risks: the log itself needs to be &lt;em&gt;monitored&lt;/em&gt; (and &lt;em&gt;audited&lt;/em&gt;&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb#fn:auditors"&gt;4&lt;/a&gt;&lt;/sup&gt;).&lt;/p&gt;
&lt;p&gt;For example, maintainers want to know if an unexpected release occurs
(indicating compromise) or if a release’s contents vary unexpectedly
(indicating host/proxy tampering). Similarly, downstreams may wish to monitor
their dependencies for unexpected activity.&lt;/p&gt;
&lt;p&gt;The transparency log operator controls submission to the log, and Go’s sumdb
is no exception. This allows the operator to decide what goes into the log:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Every module version exists exactly once in the log. In other words:
&lt;code&gt;src.example.com/foo/bar@v1.2.3&lt;/code&gt; &lt;em&gt;should&lt;/em&gt; have at most exactly one log entry.
Uniqueness&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb#fn:uniqueness"&gt;5&lt;/a&gt;&lt;/sup&gt; is not a native property of Merkle tree-based logs; it’s checked
by log auditors.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Only the Go proxy itself can submit entries to the log. External users
can &lt;em&gt;induce&lt;/em&gt; log entry creation by requesting module versions from the proxy,
but the proxy is responsible for constructing and submitting the entry
in accordance with the log’s policies.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Flexibility&lt;a href="https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb#flexibility"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So, where does that leave us?&lt;/p&gt;
&lt;p&gt;Recall that a module version is a pair of (module path, version).
&lt;a href="https://pkg.go.dev/golang.org/x/mod/module"&gt;Per &lt;code&gt;x/mod/module&lt;/code&gt;&lt;/a&gt;, the module path is the substring of a filesystem path
in the context of the download cache. However, in the context of the proxy
protocol, it’s a URL&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb#fn:https"&gt;6&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;In both of these contexts, Go correctly observes that case-sensitivity can’t
be assumed: &lt;code&gt;src.example.com/Foo/Bar&lt;/code&gt; and &lt;code&gt;src.example.com/foo/bar&lt;/code&gt; must
be treated as distinct module paths, even if a given filesystem or HTTP server
treats them as equivalent.&lt;/p&gt;
&lt;p&gt;To keep them distinct, Go uses a very small escaping scheme in
potentially-insensitive contexts: every uppercase letter
is replaced with its lowercase equivalent, prefixed by an exclamation mark (&lt;code&gt;!&lt;/code&gt;).
This is unambiguous because Go &lt;a href="https://go.dev/ref/mod#go-mod-file-ident"&gt;otherwise forbids&lt;/a&gt; &lt;code&gt;!&lt;/code&gt; in module paths.&lt;/p&gt;
&lt;p&gt;Using the example above, the module path &lt;code&gt;src.example.com/Foo/Bar&lt;/code&gt; becomes
&lt;code&gt;src.example.com/!foo/!bar&lt;/code&gt; in the proxy protocol and, by extension,
in the sumdb.&lt;/p&gt;
&lt;p&gt;The consequence of this is that there are &lt;em&gt;often&lt;/em&gt; multiple valid URL forms
that point to the same module version contents. That, in turn, means that
there are multiple valid log entries that correspond to the same module
version contents…almost.&lt;/p&gt;
&lt;p&gt;For example, here’s &lt;code&gt;github.com/google/uuid@v1.6.0&lt;/code&gt;
and &lt;code&gt;github.com/Google/uuid@v1.6.0&lt;/code&gt; pulled from the &lt;code&gt;/lookup&lt;/code&gt; API:&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
&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre&gt;% curl https://sum.golang.org/lookup/github.com/google/uuid@v1.6.0
22152757
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0&lt;span&gt;=&lt;/span&gt;
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo&lt;span&gt;=&lt;/span&gt;

go.sum database tree
48546416
cEcdZrD3Oio0/tZ9JP2gKMdqeMWAvwiLDx0G2r3MJGI&lt;span&gt;=&lt;/span&gt;

— sum.golang.org Az3grl3WPuhg73ePSVE8gQId3sd0uJ7PAxsDvyUW8JsPKKTz5JQ96wQNIgsXJGB/wLrtzXoxtXKfrgWJlDsbu7R7YgA&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;and:&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
&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre&gt;% curl &lt;span&gt;'https://sum.golang.org/lookup/github.com/!google/uuid@v1.6.0'&lt;/span&gt;
22198707
github.com/Google/uuid v1.6.0 h1:2avh7oGmXo3QQBdhUzCNHa3t06F22DZJzaton5Cp5pc&lt;span&gt;=&lt;/span&gt;
github.com/Google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo&lt;span&gt;=&lt;/span&gt;

go.sum database tree
48547430
&lt;span&gt;nz45epGyfKdGJiU29VkyCZWYt8AvqzpTogcry0Ck9m0&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;

— sum.golang.org Az3grr5LY/709J21wXS58h4kPuL0EUaVLrFdpHpRQiwfY9T7lh3gg71GDs3J40Kg6GDhj3XFEPt4u+6n4npgGNTemAg&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;Observe that the two have different log entry numbers (&lt;code&gt;22152757&lt;/code&gt; vs &lt;code&gt;22198707&lt;/code&gt;),
despite pointing to the same logical module contents. However, they &lt;em&gt;also&lt;/em&gt; have
different content hashes (the first &lt;code&gt;h1:...&lt;/code&gt;), but &lt;strong&gt;not&lt;/strong&gt; different &lt;code&gt;go.mod&lt;/code&gt; hashes.
This is because the content hash includes the module path as part of its input,
while the &lt;code&gt;go.mod&lt;/code&gt; hash is the hash of the &lt;code&gt;go.mod&lt;/code&gt; file itself, as
it appears in the logical module contents.&lt;/p&gt;
&lt;p&gt;Moreover, we can induce a &lt;em&gt;third&lt;/em&gt; log entry by requesting the module
version from the proxy using yet another URL form, such as
&lt;code&gt;https://proxy.golang.org/github.com/GOOgle/uuid/@v1.&lt;/code&gt;:&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
&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre&gt;% curl &lt;span&gt;'https://sum.golang.org/lookup/github.com/!g!o!ogle/uuid@v1.6.0'&lt;/span&gt;
48547565
github.com/GOOgle/uuid v1.6.0 h1:pwtfSDjGACNyzYVFI0EQMg8KweQ5T+2NrQKJVXoKyj0&lt;span&gt;=&lt;/span&gt;
github.com/GOOgle/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo&lt;span&gt;=&lt;/span&gt;

go.sum database tree
48547566
4x18IY7uNfTK8JVpFSg93+aVFGUoS9slyyVfEn5PRWY&lt;span&gt;=&lt;/span&gt;

— sum.golang.org Az3grgmqwzBbDjb7ojZhmPLDCyCCAreUMfcQ1b7slVrxhFAS8JpZ8wJvGqZ1FW5a8t/FxUXbnQYD2s/+V7RQICZHHgU&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;This last one took a few seconds to respond&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb#fn:sync"&gt;7&lt;/a&gt;&lt;/sup&gt;, unlike the other two. As the new log entry
number close to the signed tree head indicates, it was created on demand (since I was the first to
request it).&lt;/p&gt;
&lt;h2&gt;Implications&lt;a href="https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb#implications"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To tie things together:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go sumdb log entries are unique per module version;&lt;/li&gt;
&lt;li&gt;Go module paths (within the module version) are case-sensitive;&lt;/li&gt;
&lt;li&gt;Module paths are URLs and the path component of URLs is
&lt;em&gt;case-indeterminate&lt;/em&gt;&lt;sup&gt;&lt;a href="https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb#fn:host"&gt;8&lt;/a&gt;&lt;/sup&gt;, meaning that multiple case forms
&lt;em&gt;may&lt;/em&gt; point to the same module version contents;&lt;/li&gt;
&lt;li&gt;(1)-(3) mean that the Go proxy can be induced into creating
multiple distinct log entries that point to the same module version contents,
albeit with different content hashes.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This has a few interesting consequences:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;In practice, monitoring a module via the sumdb is not as trivial as
monitoring a single representation: the monitor needs to be aware of all
valid case forms, which in turn depends on the host. GitHub for example
has case-insensitive paths, but other hosts may not. The simplest thing
to do is probably to match on any case-folded variant of the module path
being monitored, but that may yield false positives if the host is
case-sensitive.&lt;/p&gt;
&lt;p&gt;The main (public) Go module monitor that I’m aware of is &lt;a href="https://www.gopherwatch.org"&gt;GopherWatch&lt;/a&gt;,
 which &lt;a href="https://github.com/mjl-/gopherwatch/blob/26fe1de964d2b0ce14cbf0c507ca6757b38f8da6/data.go#L126-L129"&gt;intentionally only does case-sensitive comparisons&lt;/a&gt;. This is
 probably a reasonable default for the average user (who is likely monitoring
 modules on GitHub or GitLab), but it does mean that a malicious
 case-sensitive host can evade detection.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;src.example.com/Foo/bar&lt;/code&gt; and &lt;code&gt;src.example.com/foo/bar&lt;/code&gt; &lt;em&gt;really are&lt;/em&gt; distinct
modules from Go’s perspective, even though humans have been trained to treat
them as the same (thanks to common web URL practices).
An attacker could in principle take advantage of
this to do something similar to &lt;a href="https://en.wikipedia.org/wiki/Typosquatting"&gt;typosquatting&lt;/a&gt;, where an innocent-looking
rename of &lt;code&gt;src.example.com/acmecorp/widget&lt;/code&gt; to &lt;code&gt;src.example.com/AcmeCorp/widget&lt;/code&gt;
results in completely different module contents.&lt;/p&gt;
&lt;p&gt;Or in other words: ask yourself if this diff would attract your scrutiny
 in an otherwise unobjectionable change:&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
&lt;/pre&gt;&lt;/td&gt;&lt;td&gt;&lt;pre&gt; - import "src.example.com/acmecorp/widget"
 + import "src.example.com/AcmeCorp/widget"
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;What makes this (potentially) more pernicious than “normal” typosquatting
 is that it looks unobjectionable in a way that normal typosquatting
 (like &lt;code&gt;src.example.com/acmec0rp/widget&lt;/code&gt;) wouldn’t. As evidenced by the log lookups
 earlier, it’s &lt;em&gt;already&lt;/em&gt; common enough for people to use different case
 forms (e.g. &lt;code&gt;google/uuid&lt;/code&gt; and &lt;code&gt;Google/uuid&lt;/code&gt; both already having lower entries).&lt;/p&gt;
&lt;p&gt;In practice, what holds this back is that the attacker would either need
 to control the host (to ensure that different cases route differently)
 &lt;em&gt;or&lt;/em&gt; find an existing host that does that. GitHub and GitLab both appear
 to guarantee case-insensitivity, but there may be others that don’t.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For a module path containing &lt;code&gt;N&lt;/code&gt; non-domain characters in &lt;code&gt;[a-zA-Z]&lt;/code&gt;, there are
2&lt;sup&gt;N&lt;/sup&gt; possible case variants. This doesn’t pose a problem for matching
within the monitor (we can just compare case-insensitively), but it &lt;em&gt;does&lt;/em&gt;
pose fatigue and resource risks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Using &lt;code&gt;src.example.com/acmecorp/widget&lt;/code&gt; as an example, there are 2&lt;sup&gt;14&lt;/sup&gt; = 16,384
possible case variants. An attacker who wants to obscure their activity against
that module could spam the proxy with requests for those variants, causing
alert fatigue (or dropped alerts due to volume) for the monitor.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Similarly, the attacker can force the creation of 2&lt;sup&gt;14&lt;/sup&gt; distinct log entries
for the same module version, bloating the log and wasting the proxy and log’s
compute and network resources. Anybody can already do this by submitting
junk modules to the proxy, but this is partcularly bad because it makes
amplification relatively easy: the attacker doesn’t have to create any additional
modules, and can even use pre-existing innocent module versions.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Of these, I think (2) is probably the most interesting:
(1) is something that monitors can fix by comparing module paths case-insensitively,
and (3) is just a griefing vector. The first half of (3) is also probably mitigable
by deduplicating on the &lt;code&gt;go.sum&lt;/code&gt;’s content hash, since the attacker can’t vary
that just by varying the module path’s case.&lt;/p&gt;
&lt;h2&gt;Concluding thoughts&lt;a href="https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb#concluding-thoughts"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Does this matter from a security perspective? I think no, at least not much.
The typosquatting-style attack is interesting, but assumes things about the
module host that aren’t typically true. Specifically, it assumes a
case-sensitive module host where the attacker can &lt;em&gt;in fact&lt;/em&gt; register/emplace
modules with different case forms. That’s probably uncommon in practice,
and any host that’s observed to do this intentionally would probably be
worth excluding from the Go module proxy altogether.&lt;/p&gt;
&lt;p&gt;At the most, it’s a quirk and a reminder of how subtle “identity” can be
in a transparency scheme with additional uniqueness properties. Packaging
ecosystems that aim to adopt transparency logs in a manner similar to Go’s
sumdb will almost certainly run into similar issues.&lt;/p&gt;
&lt;p&gt;It’s also a great demonstration of how &lt;em&gt;good and thoughtful&lt;/em&gt; Go’s packaging
design is: the system as a whole lacks ambiguity except where introduced by
external interactions (host filesystems, URL paths), and those ambiguities are
carefully constrained down to managable levels.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;p.s. Thanks to &lt;a href="https://filippo.io"&gt;Filippo Valsorda&lt;/a&gt; for sanity-checking some of my conclusions here,
since I am by no means a Go expert.&lt;/p&gt;
&lt;hr&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;This happens for both innocent and malicious reasons, usually because Git tags
    are mutable and can be overwritten with a force-push. &lt;a href="https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb#fnref:tag"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The semantics of including some data in a transparency log are
              governed by a &lt;a href="https://transparency.dev/pdfs/claimant-model-tutorial.pdf"&gt;claimant model&lt;/a&gt;, as well as how clients are
              expected to interact with that log. For example, interactions
              with CT logs are generally expected to be “total” in the sense
              that a leaf certificate that chains up to a public CA should
              always be logged, and the absence of an entry is a notable
              signal. In contrast, a packaging ecosystem that adopts Sigstore
              generally can’t (and shouldn’t) compel every single package
              to be signed and logged, so the absence of an entry is not
              necessarily notable absent other context. &lt;a href="https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb#fnref:claimantmodel"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This being how open source packaging works by design. &lt;a href="https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb#fnref:design"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The difference between a &lt;em&gt;monitor&lt;/em&gt; and an &lt;em&gt;auditor&lt;/em&gt; is subtle:
         a &lt;em&gt;monitor&lt;/em&gt; observes the log for specific behaviors (e.g.
         new entries matching some set of claims), while an &lt;em&gt;auditor&lt;/em&gt;
         verifies the integrity/correctness of the log itself
         (e.g. ensuring the log is actually consistent and append-only). &lt;a href="https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb#fnref:auditors"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;While writing this, I tried to find an resource that actually
           documents this property as guaranteed, but I couldn’t! However,
           it’s implied by the sumdb’s lookup API, which can only return
           a single entry per module version. It’s also mentioned indirectly
           in &lt;a href="https://raphting.dev/posts/gosumdb-live-again/"&gt;this blog post on raphting.dev&lt;/a&gt; and in Martin Hutchinson’s &lt;a href="https://github.com/mhutchinson/sumdb-audit"&gt;&lt;code&gt;sumdb-audit&lt;/code&gt;&lt;/a&gt;
           tool. The uniqueness check is also visible in the &lt;a href="https://cs.opensource.google/go/x/mod/+/refs/tags/v0.31.0:sumdb/test.go;l=96-101"&gt;TestServer in x/mod/sumdb&lt;/a&gt;.
           After discussion with Filippo, I understand why this isn’t ever described as
           “guaranteed”: uniqueness is a property that’s enforced by auditors, the
           log &lt;strong&gt;cannot&lt;/strong&gt; itself guarantee it. &lt;a href="https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb#fnref:uniqueness"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;https://&lt;/code&gt; is implied. &lt;a href="https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb#fnref:https"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Unlike Certificate Transpareny, Go’s sumdb inclusion is synchronous: the proxy
     blocks until the log entry is actually fully included, instead of yielding
     a promise to include it later. I suspect the proxy is additionally fully fetching
     the module itself for hashing, which adds more synchronous delay. &lt;a href="https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb#fnref:sync"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the sense that the path component of a URL is case sensitive, but that
     it’s up to the handling HTTP server/host to determine how it handles
     case sensitivity. &lt;a href="https://blog.yossarian.net/2025/12/29/Some-flexibility-with-Go-s-sumdb#fnref:host"&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/1pypbhh/some_flexibility_with_gos_sumdb/"&gt;Reddit&lt;/a&gt;
&lt;a href="https://infosec.exchange/@yossarian/115803589597737254"&gt;Mastodon&lt;/a&gt;
&lt;a href="https://bsky.app/profile/yossarian.net/post/3mb55zn6bqj2u"&gt;Bluesky&lt;/a&gt;
&lt;/span&gt;
&lt;hr&gt;

&lt;a href="https://blog.yossarian.net/2025/12/13/cooldowns-redux"&gt;Previously&lt;/a&gt;


&lt;a href="https://blog.yossarian.net/2026/04/11/Brocards-for-vulnerability-triage"&gt;Newer&lt;/a&gt;

&lt;/body&gt;
</ns0:encoded><pubDate>Mon, 29 Dec 2025 00:00:00 UTC</pubDate></item></channel></rss>