<?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: Farkas Zoli</title>
    <description>The latest articles on DEV Community by Farkas Zoli (@wzee).</description>
    <link>https://dev.to/wzee</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4226943%2F09993b3a-02c5-40b2-9010-b3924f6b6178.jpg</url>
      <title>DEV Community: Farkas Zoli</title>
      <link>https://dev.to/wzee</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wzee"/>
    <language>en</language>
    <item>
      <title>How I built zero-knowledge secret sharing with Web Crypto and Cloudflare Durable Objects</title>
      <dc:creator>Farkas Zoli</dc:creator>
      <pubDate>Wed, 04 Nov 2026 18:13:10 +0000</pubDate>
      <link>https://dev.to/wzee/how-i-built-zero-knowledge-secret-sharing-with-web-crypto-and-cloudflare-durable-objects-4o4n</link>
      <guid>https://dev.to/wzee/how-i-built-zero-knowledge-secret-sharing-with-web-crypto-and-cloudflare-durable-objects-4o4n</guid>
      <description>&lt;p&gt;Every team has had this moment: someone urgently needs an API token, database credential, or &lt;strong&gt;&lt;em&gt;.env&lt;/em&gt;&lt;/strong&gt; file, and the fastest path is pasting it into Slack, Teams, or Discord.&lt;/p&gt;

&lt;p&gt;The problem? That chat channel keeps the plaintext forever: in search logs, backups, exports, and on every device synced to the account.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://pass.wzee.dev" rel="noopener noreferrer"&gt;zeepass&lt;/a&gt; which is a free, open-source tool to share secrets via one-time self-destructing links.&lt;br&gt;
But my goal was stricter than just "trusting the server not to look":&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The server should physically never have what it needs to decrypt the secret.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is the exact architecture, the 150 lines of code powering it, and where the limits of browser cryptography lie.&lt;/p&gt;

&lt;h3&gt;
  
  
  How it works under the hood
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Client-side encryption:&lt;/strong&gt; Your browser generates a fresh 256-bit AES-GCM key with the Web Crypto API. Only the ciphertext and a random 12-byte IV leave your machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-knowledge URL fragment:&lt;/strong&gt; The key is appended to the URL fragment (&lt;code&gt;pass.wzee.dev/s/&amp;lt;id&amp;gt;#key=...&lt;/code&gt;). Browsers never send fragments in HTTP requests, so the server only ever sees the ID, never the key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mistyped-link protection:&lt;/strong&gt; To prevent an accidentally truncated link from burning and losing the secret forever, the browser sends a SHA-256 verifier hash of the key. The server rejects wrong keys without deleting the secret.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ephemeral storage:&lt;/strong&gt; Ciphertext lives in an isolated Cloudflare Durable Object (pinned to the EU). An automated alarm wipes it on expiration (1 hour to 7 days) or immediately upon reading.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preview-bot protection:&lt;/strong&gt; Slack, Discord, and iMessage preview bots only issue &lt;code&gt;GET&lt;/code&gt; requests. Opening the link reveals nothing; decrypting requires a button click that fires a &lt;code&gt;POST&lt;/code&gt; request (Server Action), so crawlers never accidentally burn your links.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Realistic limits &amp;amp; threat model
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trusting the served JavaScript:&lt;/strong&gt; Like every web crypto app, you trust that the host serves unaltered code. The code is MIT open source, but real-time build verification in a browser doesn't exist yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The link is the bearer token:&lt;/strong&gt; Whoever holds the full URL before it is burned can decrypt it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge metadata:&lt;/strong&gt; Cloudflare terminates TLS at the edge and processes standard connection IP logs.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Try it / Inspect the Code
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live tool:&lt;/strong&gt; &lt;a href="https://pass.wzee.dev" rel="noopener noreferrer"&gt;pass.wzee.dev&lt;/a&gt; (Free, no account, no tracking)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source code (MIT):&lt;/strong&gt; &lt;a href="https://github.com/wzee1/zeetools/tree/main/apps/zeepass" rel="noopener noreferrer"&gt;github.com/wzee1/zeetools/tree/main/apps/zeepass&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd love to hear your thoughts or any edge cases you spot!&lt;/p&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7liz8tfplbif7hlhv0nv.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7liz8tfplbif7hlhv0nv.png" alt=" " width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>nextjs</category>
      <category>security</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
