<?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: Hamza Ezzaydia</title>
    <description>The latest articles on DEV Community by Hamza Ezzaydia (@hamzaydia).</description>
    <link>https://dev.to/hamzaydia</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1881872%2F155ff76c-55b5-4173-b205-e27be5ac8eab.png</url>
      <title>DEV Community: Hamza Ezzaydia</title>
      <link>https://dev.to/hamzaydia</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hamzaydia"/>
    <language>en</language>
    <item>
      <title>I built what browsers refused to ship: SRI for fetch()</title>
      <dc:creator>Hamza Ezzaydia</dc:creator>
      <pubDate>Sun, 25 Jan 2026 23:57:46 +0000</pubDate>
      <link>https://dev.to/hamzaydia/i-built-what-browsers-refused-to-ship-sri-for-fetch-1d40</link>
      <guid>https://dev.to/hamzaydia/i-built-what-browsers-refused-to-ship-sri-for-fetch-1d40</guid>
      <description>&lt;h2&gt;
  
  
  The Problem Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Browser SRI (Subresource Integrity) has been around since 2016. It lets you verify that scripts loaded from CDNs haven't been tampered with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.example.com/lib.js"&lt;/span&gt;
        &lt;span class="na"&gt;integrity=&lt;/span&gt;&lt;span class="s"&gt;"sha256-abc123..."&lt;/span&gt;
        &lt;span class="na"&gt;crossorigin=&lt;/span&gt;&lt;span class="s"&gt;"anonymous"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the thing: &lt;strong&gt;SRI only works on &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tags.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you &lt;code&gt;fetch()&lt;/code&gt; a file - a WASM module, an AI model, a JSON config, any binary data - you get zero integrity protection. The browser just... trusts it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happens When a CDN Gets Compromised
&lt;/h2&gt;

&lt;p&gt;In June 2024, polyfill.io was compromised. The CDN started serving malicious JavaScript to over 100 million websites.&lt;/p&gt;

&lt;p&gt;Sites that used SRI on their script tags were protected. Sites that loaded polyfill.io via &lt;code&gt;fetch()&lt;/code&gt; or dynamic import? Completely vulnerable.&lt;/p&gt;

&lt;p&gt;This isn't a theoretical attack. It happened. And it will happen again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Native Solutions Fail
&lt;/h2&gt;

&lt;p&gt;You might think: "I'll just hash the response myself with &lt;code&gt;crypto.subtle.digest()&lt;/code&gt;"&lt;/p&gt;

&lt;p&gt;Try that with a 4GB AI model:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File Size&lt;/th&gt;
&lt;th&gt;Native crypto.subtle&lt;/th&gt;
&lt;th&gt;VerifyFetch&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;100 MB&lt;/td&gt;
&lt;td&gt;Works&lt;/td&gt;
&lt;td&gt;Works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 GB&lt;/td&gt;
&lt;td&gt;Slow, RAM spike&lt;/td&gt;
&lt;td&gt;2MB memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4 GB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Browser crash&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2MB memory&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Native crypto buffers the entire file into memory before hashing. That's fine for small files, but completely unusable for large ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: VerifyFetch
&lt;/h2&gt;

&lt;p&gt;I built VerifyFetch to fill this gap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;verifyFetch&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;verifyfetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;verifyFetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/model.bin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;sri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the hash doesn't match, it throws. Your users are protected.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;VerifyFetch uses a WASM streaming hasher compiled from Rust. Data flows through chunk by chunk - the file is never buffered entirely in memory.&lt;/p&gt;

&lt;p&gt;The result: constant ~2MB memory usage whether you're verifying a 10MB file or a 10GB file.&lt;/p&gt;

&lt;p&gt;It also includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fallback URLs&lt;/strong&gt;: If verification fails, automatically try a backup server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Progress tracking&lt;/strong&gt;: Know exactly how much has been verified&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manifest mode&lt;/strong&gt;: One JSON file with all your hashes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI for CI/CD&lt;/strong&gt;: Fail builds if files change after signing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Generate Hashes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx verifyfetch sign ./public/&lt;span class="k"&gt;*&lt;/span&gt;.wasm ./models/&lt;span class="k"&gt;*&lt;/span&gt;.bin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a &lt;code&gt;vf.manifest.json&lt;/code&gt; with all the SRI hashes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use in Your App
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createVerifyFetcher&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;manifestUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/vf.manifest.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Hashes are looked up automatically&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;vf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/model.bin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;vf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/config.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Enforce in CI
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx verifyfetch enforce &lt;span class="nt"&gt;--manifest&lt;/span&gt; ./public/vf.manifest.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your deploy fails if any file has changed since signing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&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;verifyfetch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub: &lt;a href="https://github.com/hamzaydia/verifyfetch" rel="noopener noreferrer"&gt;https://github.com/hamzaydia/verifyfetch&lt;/a&gt;&lt;br&gt;
Docs: &lt;a href="https://verifyfetch.com" rel="noopener noreferrer"&gt;https://verifyfetch.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd love feedback on the API design and any edge cases I might be missing.&lt;/p&gt;

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