>_ INITIALIZING DATABANKS...
>_ LOADING UI MODULES...
>_ DECRYPTING ASSETS...
>_ SECURING CONNECTION...
>_ SYSTEM READY.

Zero-Trace State Management: Decrypting in the Browser

React WebCrypto API State Management Security
USER: SYS_ADMIN | DATE: 2026-03-26 | REF_PID: Blog-Site
Cover for Zero-Trace State Management: Decrypting in the Browser

Once the Python build script packages my private logs into encrypted.json, Astro serves that file to the public as a static asset. Anyone can download it.

The challenge on the frontend was building a mechanism to unlock and read that data dynamically, without ever persisting the decrypted text to the user’s hard drive.

The WebCrypto API

Instead of importing a massive JavaScript cryptography library (which would bloat the site’s bundle size), the “Ghost Terminal” React component relies entirely on the browser’s native window.crypto.subtle API.

Because the WebCrypto API is executed at a low level by the browser engine, it is incredibly fast. When I trigger the terminal and input my password, React reverses the exact process the Python script performed during the build step:

// Extracting the payload components
const salt = Uint8Array.from(atob(encryptedPayload.salt), c => c.charCodeAt(0));
const iv = Uint8Array.from(atob(encryptedPayload.iv), c => c.charCodeAt(0));
const ciphertext = Uint8Array.from(atob(encryptedPayload.ciphertext), c => c.charCodeAt(0));

// 1. Re-derive the key from the password
const keyMaterial = await window.crypto.subtle.importKey(
  "raw", new TextEncoder().encode(password), { name: "PBKDF2" }, false, ["deriveKey"]
);

const key = await window.crypto.subtle.deriveKey(
  { name: "PBKDF2", salt, iterations: 100000, hash: "SHA-256" }, 
  keyMaterial, { name: "AES-GCM", length: 256 }, true, ["decrypt"]
);

// 2. Unlock the Vault
const decryptedBuffer = await window.crypto.subtle.decrypt(
  { name: "AES-GCM", iv }, key, ciphertext
);

The Amnesia Protocol

Decrypting the text is only half the battle. If I store the resulting JSON in React’s standard state, navigating away from the page or refreshing the browser would instantly lock me back out.

However, if I store the decrypted text in localStorage, it permanently writes that sensitive data to the browser’s disk cache. It would remain there until manually deleted, entirely defeating the purpose of the encryption.

To solve this, I utilize sessionStorage

Persistent vs. Volatile Memory

While localStorage behaves like a hard drive, sessionStorage behaves like Volatile RAM. It survives page reloads and UI navigations, but the exact millisecond the user closes the browser tab, the operating system purges the memory.

// Decode the raw buffer back into a JSON string
const decoded = new TextDecoder().decode(decryptedBuffer);

// Parse it into React State for immediate UI rendering
const parsedLogs = JSON.parse(decoded);
setDecryptedLogs(parsedLogs);

// Save strictly to Volatile RAM to survive page reloads
sessionStorage.setItem('volatileRamLogs', decoded);

When the Ghost Terminal mounts, its first action is to check sessionStorage. If the unencrypted logs exist in RAM, the terminal bypasses the password prompt entirely and instantly hydrates the UI.

This creates a seamless, app-like experience. I can read my private logs, click links, browse the public sections of the site, and return to the vault without re-entering my password. But the moment the session ends, the amnesia protocol takes over, the RAM is dumped, and the site reverts to a locked static state

0%