<?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: Graysoft Dev</title>
    <description>The latest articles on DEV Community by Graysoft Dev (@graysoftdev).</description>
    <link>https://dev.to/graysoftdev</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%2F3804190%2F943d6cad-f853-466c-92ed-51ed21d57099.png</url>
      <title>DEV Community: Graysoft Dev</title>
      <link>https://dev.to/graysoftdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/graysoftdev"/>
    <language>en</language>
    <item>
      <title>I built a zero-knowledge file sharing app as a college student — here's what I learned about AES-256-GCM in the browser</title>
      <dc:creator>Graysoft Dev</dc:creator>
      <pubDate>Fri, 13 Mar 2026 13:50:41 +0000</pubDate>
      <link>https://dev.to/graysoftdev/i-built-a-zero-knowledge-file-sharing-app-as-a-college-student-heres-what-i-learned-about-ald</link>
      <guid>https://dev.to/graysoftdev/i-built-a-zero-knowledge-file-sharing-app-as-a-college-student-heres-what-i-learned-about-ald</guid>
      <description>&lt;p&gt;About a year ago, I was in my dorm room copying a 4GB project file to a flash drive to walk across campus. It felt absurd. Every cloud service either had file size limits, showed ads, or asked me to trust them with my data. So I started building something.&lt;/p&gt;

&lt;p&gt;That project became &lt;a href="https://fileshot.io" rel="noopener noreferrer"&gt;FileShot.io&lt;/a&gt; — a zero-knowledge, end-to-end encrypted file sharing platform. This post isn't a product pitch. It's about the specific technical problem I had to solve: doing AES-256-GCM encryption entirely in the browser, before a single byte of data leaves the user's machine.&lt;/p&gt;




&lt;h2&gt;
  
  
  What "zero-knowledge" actually means here
&lt;/h2&gt;

&lt;p&gt;Zero-knowledge architecture means the server genuinely cannot read your files. Not "we promise not to look" — the server &lt;em&gt;can't&lt;/em&gt; look, because it never has the decryption key.&lt;/p&gt;

&lt;p&gt;The key is derived from a password on the client side, and it lives only in the URL fragment (&lt;code&gt;#&lt;/code&gt;) — the part of the URL the browser never sends to the server. When someone visits a FileShot link, their browser downloads the encrypted blob and derives the key locally from the fragment. The server sees only ciphertext.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Web Crypto API: what I wish someone had explained to me
&lt;/h2&gt;

&lt;p&gt;When I started, I assumed I'd use a library. Turns out browsers ship a robust cryptographic API natively: &lt;code&gt;window.crypto.subtle&lt;/code&gt;. It's available in all modern browsers, hardware-accelerated where possible, and designed for exactly this.&lt;/p&gt;

&lt;p&gt;Here's a minimal AES-256-GCM encryption flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;encryptFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&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;encoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&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;salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRandomValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&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;keyMaterial&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;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;importKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;raw&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PBKDF2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deriveKey&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&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;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deriveKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PBKDF2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;310000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SHA-256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;keyMaterial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AES-GCM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;encrypt&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRandomValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&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;ciphertext&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;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AES-GCM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;fileBuffer&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ciphertext&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things I learned the hard way:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The IV matters more than most tutorials imply.&lt;/strong&gt; AES-GCM is an authenticated encryption scheme. The 96-bit IV must be random and unique per encryption operation — reusing an IV with the same key is a catastrophic failure that can expose the key stream entirely. &lt;code&gt;crypto.getRandomValues&lt;/code&gt; is the right call here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GCM gives you integrity checking for free.&lt;/strong&gt; The authentication tag appended to the ciphertext fails verification if anyone tampers with even a single bit. No separate HMAC step needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PBKDF2 iteration count matters.&lt;/strong&gt; 310,000 iterations is OWASP's current recommendation for PBKDF2-SHA256. I started with 100,000 — fine for 2020, outdated today. The slowdown is a few hundred milliseconds on weak hardware. Worth it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I didn't expect: streaming large files
&lt;/h2&gt;

&lt;p&gt;The naive implementation — &lt;code&gt;file.arrayBuffer()&lt;/code&gt; then encrypt the whole thing — works great until someone tries to upload a 2GB video. Then the tab crashes.&lt;/p&gt;

&lt;p&gt;I rewrote the encryption layer to use the Streams API. Read the file in chunks, encrypt each chunk, stream the encrypted data directly to the upload. This was one of the harder parts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;streamEncryptChunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&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;CHUNK_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 5MB&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&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;slice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;CHUNK_SIZE&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;buffer&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;slice&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRandomValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&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;encrypted&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;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AES-GCM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;buffer&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encrypted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;CHUNK_SIZE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each chunk gets its own IV. Using the same IV across chunks would be a vulnerability — an attacker could compare ciphertext blocks to learn something about repeated plaintext patterns.&lt;/p&gt;

&lt;p&gt;In production there's more to it: multipart uploads, a Web Worker for crypto so the UI doesn't freeze, upload progress tracking.&lt;/p&gt;




&lt;h2&gt;
  
  
  The URL fragment trick
&lt;/h2&gt;

&lt;p&gt;The zero-knowledge design depends on the &lt;code&gt;#fragment&lt;/code&gt; portion of the URL never being sent to servers. Browsers don't include it in HTTP requests.&lt;/p&gt;

&lt;p&gt;When a user uploads and sets a password, key material is encoded into the URL fragment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://fileshot.io/f/abc123#keydata=...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anyone with the full URL can decrypt in their browser. Anyone intercepting only the HTTP traffic gets nothing useful. The server storing the encrypted blob has no idea what's in it.&lt;/p&gt;

&lt;p&gt;This pattern is well-established — Keybase used it, some Signal features use it — but implementing it from scratch teaches you exactly why it works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Things I'd do differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Argon2 instead of PBKDF2.&lt;/strong&gt; Argon2id is memory-hard and significantly more resistant to GPU cracking. PBKDF2 is fine and widely supported, but if starting over I'd use Argon2 via WebAssembly. No native Web Crypto API support means shipping a WASM module — worth the tradeoff for a new project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Separate the encryption key from access control.&lt;/strong&gt; The current design couples "the key to decrypt" with "permission to access." A cleaner design uses asymmetric key wrapping — the file's AES key encrypted with the recipient's public key. But that requires accounts, which adds friction most casual users won't accept for a simple file share.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A real cryptographic audit before launch.&lt;/strong&gt; I self-audited, which is the worst kind of audit. I'm too close to the code to catch subtle issues in my own mental model.&lt;/p&gt;




&lt;p&gt;If you're building something with client-side crypto, &lt;code&gt;crypto.subtle&lt;/code&gt; is more capable than I expected. The biggest hurdles were streaming large files without crashing the browser and understanding exactly when GCM's authentication tag is computed and verified.&lt;/p&gt;

&lt;p&gt;The project is at &lt;a href="https://fileshot.io" rel="noopener noreferrer"&gt;fileshot.io&lt;/a&gt; — unlimited free file sharing, no account needed, encrypted in your browser before upload. Questions or pushback on the crypto choices welcome in the comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>javascript</category>
      <category>privacy</category>
    </item>
    <item>
      <title>How I Built Zero-Knowledge File Sharing Using the Web Crypto API (No Server Ever Sees Your Data)</title>
      <dc:creator>Graysoft Dev</dc:creator>
      <pubDate>Wed, 04 Mar 2026 21:15:35 +0000</pubDate>
      <link>https://dev.to/graysoftdev/how-i-built-zero-knowledge-file-sharing-using-the-web-crypto-api-no-server-ever-sees-your-data-28cp</link>
      <guid>https://dev.to/graysoftdev/how-i-built-zero-knowledge-file-sharing-using-the-web-crypto-api-no-server-ever-sees-your-data-28cp</guid>
      <description>&lt;p&gt;When I built &lt;a href="https://fileshot.io" rel="noopener noreferrer"&gt;FileShot&lt;/a&gt;, I had one hard requirement: &lt;strong&gt;the server must never be able to read a user's file — ever.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not encrypted at rest by a key the server holds. Not TLS. I mean genuinely zero-knowledge: the encryption key is generated in the browser, used in the browser, and never transmitted anywhere.&lt;/p&gt;

&lt;p&gt;Here's how I did it using the Web Crypto API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Zero-Knowledge?
&lt;/h2&gt;

&lt;p&gt;Most file sharing services say "your files are encrypted" — but they mean encrypted with &lt;em&gt;their&lt;/em&gt; key. If their database leaks, or a subpoena hits, all your files are exposed. True zero-knowledge means even the developer can't read the files.&lt;/p&gt;

&lt;p&gt;The Web Crypto API is built into every modern browser and gives us access to hardware-backed cryptography. No npm packages, no external dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Encryption Flow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Generate the Key
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&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;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AES-GCM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;encrypt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;decrypt&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AES-GCM is authenticated encryption — it provides both confidentiality and integrity. If anyone tampers with the ciphertext, decryption will fail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Encrypt the File
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;encryptFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&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;iv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRandomValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&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;fileBuffer&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;file&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ciphertext&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;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AES-GCM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;fileBuffer&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The IV (initialization vector) must be unique per encryption — never reuse an IV with the same key. &lt;code&gt;crypto.getRandomValues()&lt;/code&gt; is cryptographically secure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Share the Key — In the URL Fragment
&lt;/h3&gt;

&lt;p&gt;Here's the clever part: the decryption key needs to reach the recipient, but can't go through the server. We embed the exported key in the &lt;strong&gt;URL fragment&lt;/strong&gt; (the # part):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exported&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;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exportKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;raw&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&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;keyHex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exported&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&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="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&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;shareUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://fileshot.io/d/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;fileId&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;keyHex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why the fragment?&lt;/strong&gt; URL fragments are never sent to the server in HTTP requests. The server sees &lt;code&gt;/d/FILE_ID&lt;/code&gt; — it never sees the &lt;code&gt;#KEYHEX&lt;/code&gt; part. This is a standard technique for client-side key transport.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Decrypt on Download
&lt;/h3&gt;

&lt;p&gt;When someone opens the share link, the decryption key is read from &lt;code&gt;window.location.hash&lt;/code&gt; — entirely in-browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;decryptFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;keyHex&lt;/span&gt;&lt;span class="p"&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;keyBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keyHex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/.&lt;/span&gt;&lt;span class="se"&gt;{2}&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&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;key&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;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;importKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;raw&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;keyBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AES-GCM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;decrypt&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plaintext&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;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AES-GCM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;ciphertext&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What the Server Actually Stores
&lt;/h2&gt;

&lt;p&gt;The server stores only:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;encrypted ciphertext&lt;/strong&gt; (AES-256-GCM)&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;IV&lt;/strong&gt; (not secret — required for decryption, safe to store)&lt;/li&gt;
&lt;li&gt;File metadata (original filename, size, expiry, upload timestamp)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It &lt;strong&gt;never&lt;/strong&gt; has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The plaintext file content&lt;/li&gt;
&lt;li&gt;The decryption key&lt;/li&gt;
&lt;li&gt;Any way to derive the key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if you subpoena FileShot, there is nothing to hand over.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser Compatibility
&lt;/h2&gt;

&lt;p&gt;The Web Crypto API is supported in all modern browsers: Chrome 37+, Firefox 34+, Safari 11+, Edge 12+. It is &lt;strong&gt;only available in secure contexts (HTTPS or localhost)&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;You can see this in action at &lt;a href="https://fileshot.io" rel="noopener noreferrer"&gt;FileShot.io&lt;/a&gt; — upload any file and the generated share link contains the decryption key in the URL fragment. Open DevTools Network tab and confirm the key never appears in any request.&lt;/p&gt;

&lt;p&gt;The full encryption/decryption happens under 50ms even for multi-megabyte files, thanks to AES-GCM's hardware acceleration via AES-NI instructions on modern CPUs.&lt;/p&gt;




&lt;p&gt;Zero-knowledge architecture is one of those things that sounds complicated but is surprisingly approachable with the Web Crypto API. If you're building any kind of file sharing, messaging, or notes app and care about user privacy, this pattern is worth adding to your toolkit.&lt;/p&gt;

&lt;p&gt;Questions or feedback? Drop them in the comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>javascript</category>
      <category>privacy</category>
    </item>
    <item>
      <title>Indie-operated tech and gaming news — no paywalls, no sponsored content (iByte)</title>
      <dc:creator>Graysoft Dev</dc:creator>
      <pubDate>Tue, 03 Mar 2026 15:45:16 +0000</pubDate>
      <link>https://dev.to/graysoftdev/indie-operated-tech-and-gaming-news-no-paywalls-no-sponsored-content-ibyte-5fif</link>
      <guid>https://dev.to/graysoftdev/indie-operated-tech-and-gaming-news-no-paywalls-no-sponsored-content-ibyte-5fif</guid>
      <description>&lt;h2&gt;
  
  
  Why I started iByte
&lt;/h2&gt;

&lt;p&gt;The big tech news sites have become corporate PR machines. Too many articles are just rewritten press releases. Too much "sponsored content" mixed in with actual news.&lt;/p&gt;

&lt;p&gt;iByte is indie-operated tech and gaming news for developers and enthusiasts who want real coverage without the noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we cover
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Developer tools and open source releases worth knowing about&lt;/li&gt;
&lt;li&gt;Indie game launches and updates&lt;/li&gt;
&lt;li&gt;AI/ML news that actually affects developers&lt;/li&gt;
&lt;li&gt;Security vulnerabilities and patches&lt;/li&gt;
&lt;li&gt;Hardware releases relevant to devs and gamers&lt;/li&gt;
&lt;li&gt;Startup news in the indie/developer space&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What we don't do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Paywalls&lt;/li&gt;
&lt;li&gt;Sponsored "journalism"&lt;/li&gt;
&lt;li&gt;Cookie consent walls on the homepage&lt;/li&gt;
&lt;li&gt;AI-generated content or scraped summaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The site is updated daily. Everything is written or curated manually. If it makes it to iByte, I've read it and think it's worth your time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow the latest
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://ibyte.site" rel="noopener noreferrer"&gt;https://ibyte.site&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;RSS feed available. If you're tired of mainstream tech media, give it a look.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>news</category>
      <category>showdev</category>
      <category>startup</category>
    </item>
    <item>
      <title>Automated crypto trading bots at $1/month per bot — no coding required (ZipDex)</title>
      <dc:creator>Graysoft Dev</dc:creator>
      <pubDate>Tue, 03 Mar 2026 15:39:29 +0000</pubDate>
      <link>https://dev.to/graysoftdev/automated-crypto-trading-bots-at-1month-per-bot-no-coding-required-zipdex-njn</link>
      <guid>https://dev.to/graysoftdev/automated-crypto-trading-bots-at-1month-per-bot-no-coding-required-zipdex-njn</guid>
      <description>&lt;h2&gt;
  
  
  The problem with existing crypto bots
&lt;/h2&gt;

&lt;p&gt;Most crypto bot platforms either:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cost $50-200/month&lt;/li&gt;
&lt;li&gt;Lock you into their preset strategies&lt;/li&gt;
&lt;li&gt;Require you to set up and run your own infrastructure&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;ZipDex is different: $1/bot/month, full strategy customization, fully hosted.&lt;/p&gt;

&lt;h2&gt;
  
  
  What ZipDex is
&lt;/h2&gt;

&lt;p&gt;A platform to build, configure, and run automated crypto trading bots. No coding required.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Build bots with a visual strategy builder — set entry/exit conditions, stop-loss, take-profit&lt;/li&gt;
&lt;li&gt;Backtest strategies against historical data before going live&lt;/li&gt;
&lt;li&gt;Monitor bot performance in real-time&lt;/li&gt;
&lt;li&gt;Run multiple bots on different trading pairs simultaneously&lt;/li&gt;
&lt;li&gt;Works with major exchanges (Binance, Coinbase, Kraken)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pricing model
&lt;/h2&gt;

&lt;p&gt;$1/bot/month. Flat fee. No percentage of profits taken.&lt;/p&gt;

&lt;p&gt;Running 5 bots costs $5/month total. That's it.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;&lt;a href="https://zipdex.io" rel="noopener noreferrer"&gt;https://zipdex.io&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Free trial available. If you've been trading manually and want to automate your strategies without paying enterprise prices, this is built for you.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>A marketplace for buying and selling indie developer projects (iStack)</title>
      <dc:creator>Graysoft Dev</dc:creator>
      <pubDate>Tue, 03 Mar 2026 15:39:06 +0000</pubDate>
      <link>https://dev.to/graysoftdev/a-marketplace-for-buying-and-selling-indie-developer-projects-istack-4195</link>
      <guid>https://dev.to/graysoftdev/a-marketplace-for-buying-and-selling-indie-developer-projects-istack-4195</guid>
      <description>&lt;h2&gt;
  
  
  The problem on both sides
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;As a developer who abandons projects:&lt;/strong&gt; You put real work into something, then move on. That code sits on GitHub doing nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;As a developer who wants a head start:&lt;/strong&gt; You want to build something but don't want to start from zero. Working code is worth money.&lt;/p&gt;

&lt;p&gt;iStack connects both sides.&lt;/p&gt;

&lt;h2&gt;
  
  
  What iStack is
&lt;/h2&gt;

&lt;p&gt;A marketplace for indie developers to buy and sell small software projects. Not enterprise acquisitions — side projects, tools, small SaaS apps, browser extensions.&lt;/p&gt;

&lt;p&gt;Price range: $50 to $5,000.&lt;/p&gt;

&lt;h2&gt;
  
  
  For sellers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;List your project and set your price&lt;/li&gt;
&lt;li&gt;Get fair value for work you've already done&lt;/li&gt;
&lt;li&gt;Transfer ownership securely through the platform&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  For buyers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Start with working code, not just an idea&lt;/li&gt;
&lt;li&gt;Browse by tech stack, category, or revenue&lt;/li&gt;
&lt;li&gt;Ask questions before buying&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;&lt;a href="https://istack.site" rel="noopener noreferrer"&gt;https://istack.site&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you've got a project collecting dust, it might be worth more than you think.&lt;/p&gt;

</description>
      <category>saas</category>
      <category>showdev</category>
      <category>sideprojects</category>
      <category>startup</category>
    </item>
    <item>
      <title>Skip the SaaS foundation work: auth, payments, and user management pre-built (DiggaByte)</title>
      <dc:creator>Graysoft Dev</dc:creator>
      <pubDate>Tue, 03 Mar 2026 15:33:02 +0000</pubDate>
      <link>https://dev.to/graysoftdev/skip-the-saas-foundation-work-auth-payments-and-user-management-pre-built-diggabyte-1jeo</link>
      <guid>https://dev.to/graysoftdev/skip-the-saas-foundation-work-auth-payments-and-user-management-pre-built-diggabyte-1jeo</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Building a new SaaS from scratch? You'll spend the first 2-4 weeks just wiring up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User authentication (email + OAuth)&lt;/li&gt;
&lt;li&gt;Stripe subscription billing&lt;/li&gt;
&lt;li&gt;Email notifications&lt;/li&gt;
&lt;li&gt;Admin dashboard&lt;/li&gt;
&lt;li&gt;User roles and permissions&lt;/li&gt;
&lt;li&gt;Database schema and migrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before writing a single line of actual product code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What DiggaByte is
&lt;/h2&gt;

&lt;p&gt;DiggaByte is a set of production-ready SaaS boilerplate templates. You pick your stack, buy once, and skip the foundation phase entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's included
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;User auth (email/password + Google/GitHub OAuth)&lt;/li&gt;
&lt;li&gt;Stripe billing (subscriptions + one-time payments)&lt;/li&gt;
&lt;li&gt;Admin dashboard with user management&lt;/li&gt;
&lt;li&gt;Email via Resend/SendGrid with templates&lt;/li&gt;
&lt;li&gt;Feature flags and role-based access&lt;/li&gt;
&lt;li&gt;Onboarding flow&lt;/li&gt;
&lt;li&gt;Database migrations&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Available stacks
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Next.js + Prisma + PostgreSQL&lt;/li&gt;
&lt;li&gt;Express + React + MongoDB&lt;/li&gt;
&lt;li&gt;More being added&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;&lt;a href="https://diggabyte.com" rel="noopener noreferrer"&gt;https://diggabyte.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One-time purchase, lifetime updates. If you're about to build your nth SaaS and reinvent the same wheel again, take a look.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>I built a free SEO auditor that checks 100 pages — no signup required (SEODoc)</title>
      <dc:creator>Graysoft Dev</dc:creator>
      <pubDate>Tue, 03 Mar 2026 15:32:35 +0000</pubDate>
      <link>https://dev.to/graysoftdev/i-built-a-free-seo-auditor-that-checks-100-pages-no-signup-required-seodoc-31c0</link>
      <guid>https://dev.to/graysoftdev/i-built-a-free-seo-auditor-that-checks-100-pages-no-signup-required-seodoc-31c0</guid>
      <description>&lt;h2&gt;
  
  
  Why I built SEODoc
&lt;/h2&gt;

&lt;p&gt;I kept paying $100+/month for SEO tools, or hitting 3-page free limits and then a paywall. So I built my own.&lt;/p&gt;

&lt;p&gt;SEODoc audits up to 100 pages of any website for free. No account needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it checks
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Meta titles and descriptions (missing, too long, duplicate)&lt;/li&gt;
&lt;li&gt;H1/H2/H3 heading structure&lt;/li&gt;
&lt;li&gt;Image alt text coverage&lt;/li&gt;
&lt;li&gt;Internal and external link analysis&lt;/li&gt;
&lt;li&gt;Page speed signals&lt;/li&gt;
&lt;li&gt;Canonical tags and redirect chains&lt;/li&gt;
&lt;li&gt;Mobile-friendly indicators&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Enter a URL, hit go. SEODoc crawls up to 100 pages and returns a full report. Export as PDF or JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who it's for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Indie developers checking their own sites&lt;/li&gt;
&lt;li&gt;Freelancers doing quick audits for clients&lt;/li&gt;
&lt;li&gt;Anyone wanting actionable SEO data without enterprise pricing&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;&lt;a href="https://seodoc.site" rel="noopener noreferrer"&gt;https://seodoc.site&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No credit card. No signup. Just paste your URL and go.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>sideprojects</category>
      <category>tooling</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Free AI coding assistant in your browser — no signup, no install (Pocket guIDE)</title>
      <dc:creator>Graysoft Dev</dc:creator>
      <pubDate>Tue, 03 Mar 2026 15:25:46 +0000</pubDate>
      <link>https://dev.to/graysoftdev/free-ai-coding-assistant-in-your-browser-no-signup-no-install-pocket-guide-31c0</link>
      <guid>https://dev.to/graysoftdev/free-ai-coding-assistant-in-your-browser-no-signup-no-install-pocket-guide-31c0</guid>
      <description>&lt;h2&gt;
  
  
  What is Pocket guIDE?
&lt;/h2&gt;

&lt;p&gt;Pocket guIDE is a free AI coding assistant that runs entirely in your browser. No signup. No install. No cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem it solves
&lt;/h2&gt;

&lt;p&gt;Most AI coding tools create friction before you start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create an account first&lt;/li&gt;
&lt;li&gt;Set up an API key
&lt;/li&gt;
&lt;li&gt;Install a plugin or extension&lt;/li&gt;
&lt;li&gt;Pay monthly to unlock features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pocket guIDE removes all of that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Ask coding questions in plain English&lt;/li&gt;
&lt;li&gt;Get code snippets explained line by line
&lt;/li&gt;
&lt;li&gt;Debug errors by pasting them in&lt;/li&gt;
&lt;li&gt;Generate boilerplate for common patterns&lt;/li&gt;
&lt;li&gt;Works on any modern browser&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;&lt;a href="https://pocket.graysoft.dev" rel="noopener noreferrer"&gt;https://pocket.graysoft.dev&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No login required. Good for quick sessions when you don't want to spin up a full IDE.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>showdev</category>
      <category>tooling</category>
    </item>
    <item>
      <title>I built a local LLM IDE that works completely offline — guIDE</title>
      <dc:creator>Graysoft Dev</dc:creator>
      <pubDate>Tue, 03 Mar 2026 15:24:13 +0000</pubDate>
      <link>https://dev.to/graysoftdev/i-built-a-local-llm-ide-that-works-completely-offline-guide-15g</link>
      <guid>https://dev.to/graysoftdev/i-built-a-local-llm-ide-that-works-completely-offline-guide-15g</guid>
      <description>&lt;h2&gt;
  
  
  What is guIDE?
&lt;/h2&gt;

&lt;p&gt;guIDE is a local LLM IDE that lets you run AI coding models completely offline on your own hardware. No API keys, no subscription, no data leaving your machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;Every AI coding tool I tried had the same problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monthly fees ($20-100/month)&lt;/li&gt;
&lt;li&gt;Your code goes to someone else's server&lt;/li&gt;
&lt;li&gt;Hard dependency on external API uptime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;guIDE eliminates all three. Download a model once, run it locally forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Chat with a local LLM about your code&lt;/li&gt;
&lt;li&gt;Inline code completions and explanations&lt;/li&gt;
&lt;li&gt;Works with any GGUF model (Mistral, LLaMA, CodeLlama, Phi, etc.)&lt;/li&gt;
&lt;li&gt;Fully offline after initial model download&lt;/li&gt;
&lt;li&gt;No usage limits, no subscription&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;No login required. Just download and run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://graysoft.dev" rel="noopener noreferrer"&gt;https://graysoft.dev&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What local models are you running for coding? Would love to compare notes.&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
  </channel>
</rss>
