Zero-knowledge encryption, explained

Your prompts are encrypted in your browser before they reach our servers. We store ciphertext. We cannot decrypt it. This page explains exactly how that works, and what it does and does not protect against.

The short version

Every prompt you store in Prompt Cellar is encrypted with AES-256-GCM using a random 96-bit IV. The encryption key lives in your browser's memory. It is derived from your passkey using the WebAuthn PRF extension and HKDF-SHA-256. The key is never transmitted to our servers.

Our database contains encrypted blobs. Without your passkey, those blobs are indistinguishable from random bytes. This is not a policy decision. It is a property of the cryptographic scheme.

If you want the full picture, keep reading.

Key derivation

How your encryption key is created and why we never see it.

Step 1: Passkey authentication with PRF

When you authenticate, your passkey provider executes the WebAuthn PRF (Pseudo-Random Function) extension. This produces a deterministic 32-byte secret derived from a salt unique to your vault. The PRF output never leaves the authenticator boundary in plaintext during the WebAuthn ceremony—it goes directly to the browser's WebCrypto context.

Step 2: HKDF-SHA-256 key derivation

The PRF output is fed into HKDF (HMAC-based Key Derivation Function) with SHA-256. The info string is weldedanvil-vault-wrap-v1. This produces a 256-bit wrapping key. The wrapping key is non-exportable—it exists only inside the WebCrypto context and cannot be extracted by JavaScript.

Step 3: Master key unwrap

Your vault has a randomly generated AES-256-GCM master key. This master key is wrapped (encrypted) with the wrapping key from Step 2 and stored on the server. At unlock time, the wrapped master key is downloaded and unwrapped locally. The plaintext master key exists only in your browser's memory.

What the server stores: the wrapped (encrypted) master key, a 96-bit wrap IV, and the PRF salt. None of these are useful without the passkey that produced the PRF output. The wrapping key and plaintext master key never leave your browser.

Per-prompt encryption

Every prompt gets its own IV. Reusing an IV with AES-GCM would be catastrophic, so we don't.

AES-256-GCM with random 96-bit IV

Each prompt is encrypted individually using AES-256-GCM. A fresh 96-bit IV is generated via crypto.getRandomValues() for every encryption operation. The IV is stored alongside the ciphertext. GCM provides both confidentiality and integrity—any tampering with the ciphertext causes decryption to fail.

Encryption happens in the browser

The WebCrypto API does the actual encryption. The plaintext prompt is UTF-8 encoded and passed to crypto.subtle.encrypt(). The resulting ciphertext is base64url-encoded and sent to the server. The server never sees the plaintext prompt or the encryption key.

Base64url encoding, not hex

Ciphertext and IVs are encoded with base64url (RFC 4648) for compact storage. This is a wire format choice, not a security property. The ciphertext blob stored on our servers is the GCM ciphertext plus the 128-bit authentication tag appended by the WebCrypto API.

Encrypted search index

Search works without the server knowing what you searched for.

Index built client-side

When a prompt is captured, search tokens are extracted from the plaintext in your browser. These tokens (along with metadata like repo name, branch, and tool) are encrypted as a JSON blob using the same AES-256-GCM master key and a fresh IV. The server stores the encrypted token blob.

Query execution is local

When you search, the encrypted index entries are fetched in bulk and decrypted in your browser in parallel batches. The search query runs against the decrypted tokens in memory. The server sees that you requested the index. It does not see what you searched for or which results matched.

Tradeoff: index must fit in memory

Client-side search means the entire decrypted index lives in browser memory. This works well for thousands of prompts. It would not scale to millions. We chose this tradeoff deliberately: the alternative is server-side search, which requires the server to see your search tokens. We picked privacy over scale.

What the server can and cannot see

A concrete breakdown. No hand-waving.

Server can see

  • That you have an account
  • How many encrypted prompts you have stored
  • When prompts were created (timestamps)
  • The size of each encrypted blob
  • Your wrapped (encrypted) master key
  • That you performed a search (not the query)

Server cannot see

  • × The content of any prompt
  • × Your search queries or results
  • × Your encryption key (master or wrapping)
  • × Which AI tool a prompt was sent to
  • × Repository names, branch names, or project paths
  • × Search tokens or index content

Threat model: what happens in a breach

Every encryption scheme has limits. Here is where ours are.

Scenario: full database dump

An attacker gets a complete copy of our database. They have every encrypted prompt, every wrapped master key, every IV and PRF salt.

Result: They have ciphertext. To decrypt any prompt, they need the corresponding master key. To unwrap the master key, they need the wrapping key. To derive the wrapping key, they need the PRF output. To get the PRF output, they need your passkey's private key, which lives in your authenticator hardware. The data is useless without the passkey.

Scenario: compromised server code

An attacker modifies the server to log data or inject malicious responses.

Result: The server never receives plaintext prompts or encryption keys during normal operation, so passive logging reveals nothing. However, the server serves the JavaScript that runs in your browser. A compromised server could serve modified JavaScript that exfiltrates your key or plaintext. This is the primary trust boundary. You trust that the JavaScript we serve is the JavaScript we wrote.

Scenario: malicious browser extension

A browser extension with page access reads DOM content or intercepts WebCrypto calls.

Result: Game over. A browser extension with sufficient permissions can read decrypted prompts from the DOM, extract keys from memory, or intercept API calls. Client-side encryption does not protect against a compromised browser environment. This is true of every zero-knowledge web application.

Scenario: stolen passkey

An attacker obtains your passkey (e.g., compromised password manager, cloned hardware key).

Result: With the passkey and a database dump, the attacker can derive the wrapping key, unwrap the master key, and decrypt all prompts. Passkey security is the foundation. Use a hardware authenticator or a reputable password manager with PRF support.

How this compares to "encryption at rest"

Most SaaS products encrypt your data at rest. That protects against a very specific threat. It does not protect against the provider.

Encryption at rest (what most services do)

The provider encrypts your data when it is written to disk. The provider holds the encryption key. The data is decrypted when the provider's application reads it.

Protects against: someone stealing a hard drive from the data center. That's it. The provider, their employees, a subpoena, a compromised admin account—all of these can read your data because the provider holds the key.

Zero-knowledge encryption (what Prompt Cellar does)

You encrypt your data before it reaches the server. You hold the encryption key. The server stores ciphertext and has no mechanism to decrypt it.

Protects against: database breaches, rogue employees, subpoenas for stored data, compromised admin accounts, and the provider itself. The server is a dumb storage layer. It cannot read what it stores.

Threat At Rest Zero-Knowledge
Stolen hard drive
Database dump
Rogue employee
Subpoena for stored data
Compromised admin account
Malicious browser extension
Compromised JS delivery

What this does not protect against

No encryption scheme covers everything. Here is what falls outside our threat model.

×

Compromised browser environment

Malicious extensions, browser-level malware, or a compromised OS can read decrypted data from memory or the DOM. Zero-knowledge encryption operates above the browser trust boundary. If your browser is compromised, the encryption is irrelevant.

×

Malicious JavaScript served by us

If an attacker compromises our deployment pipeline and serves modified JavaScript, that code could exfiltrate your key before encryption happens. This is the fundamental limitation of web-based zero-knowledge systems. Native applications with pinned binaries do not have this problem. We mitigate this with integrity checks and deployment controls, but we cannot eliminate it.

×

Traffic analysis

An observer can see that you are communicating with Prompt Cellar, how often, and the approximate size of encrypted payloads. The content is hidden, but the pattern of usage is not. We do not pad ciphertext to a uniform size.

×

Lost passkey

If you lose access to your passkey and have no backup, your data is gone. We cannot recover it. The master key is wrapped with a key derived from your passkey. No passkey, no master key, no data. This is the cost of zero-knowledge: there is no password reset.

Implementation specifics

For the auditors and the curious.

Encryption algorithm AES-256-GCM
IV length 96 bits (12 bytes)
Auth tag length 128 bits (WebCrypto default)
IV generation crypto.getRandomValues()
Key derivation HKDF-SHA-256
KDF input WebAuthn PRF output
HKDF info string weldedanvil-vault-wrap-v1
Master key size 256 bits
PRF salt length 256 bits (32 bytes)
Ciphertext encoding Base64url (RFC 4648)
Crypto API Web Crypto (SubtleCrypto)

Your prompts. Your keys. Your data.

First 100 prompts are free. No credit card required.

Get Started