Bring your own key.We never see it.
Your API keys are encrypted locally in your browser and never sent to our servers. We designed the system so that we cannot read your keys, even if we wanted to.
- Zero-knowledge: we never see your keys.
- Client-side encryption with Web Crypto.
- Session-only passphrase.
How It Works
A transparent, client-side encryption process.
1. You Enter Your Keys
Your passphrase and API keys are entered directly into the browser. They are only held in memory for your current session.
2. Encryption in Browser
Your key is encrypted with AES-256-GCM using your passphrase via the Web Crypto API.
3. Local Storage
Only the encrypted blob is saved in your browser's IndexedDB. Nothing readable is ever sent to our servers.
We designed the app so that your unencrypted API keys never leave your browser. Only encrypted blobs are stored locally on your device, and we never receive your raw keys or your master passphrase.
How It Works in Practice
A step-by-step, non-technical explanation of our commitment to your privacy.
1. You Create a Master Passphrase
This passphrase only lives in your browser’s memory while you’re using the app. We never save it, and it is never sent to our servers. Think of it as your personal lock that controls access to all your keys.
2. Your API keys are encrypted before being stored
When you add a key (e.g. for Groq or Perplexity), we encrypt it in your browser before saving it anywhere, using modern encryption (AES-256-GCM) and a strong key derivation process (PBKDF2).
3. Encrypted data is stored only on your device
The app stores only the encrypted version of your keys in your browser’s local database (IndexedDB). Our server never sees your raw key or passphrase. Even if our database was hacked, the attacker wouldn’t get your keys, because we simply don’t have them.
4. When you use the app, decryption happens in memory
When needed, the app uses your passphrase to decrypt the key only in your browser’s memory. The decrypted key is used to call the provider’s API and then discarded. We never log it or send it anywhere else.
Code & Components
A transparent look at the open-source components that power our client-side security.
In-Memory Passphrase Management
The VaultSessionProvider uses a React Context to hold your master passphrase in your browser's memory. It is never written to disk and is cleared when you close the tab or end the session.
'use client';
import React, { createContext, useContext, useState, ReactNode, useMemo, useCallback } from 'react';
// ... (imports)
interface VaultSessionContextType {
masterPassphrase: string | null;
// ... (other types)
}
const VaultSessionContext = createContext<VaultSessionContextType | undefined>(undefined);
export const VaultSessionProvider = ({ children }: { children: ReactNode }) => {
const [masterPassphrase, setMasterPassphrase] = useState<string | null>(null);
// ... (other state)
const value = useMemo(() => ({
masterPassphrase,
setMasterPassphrase,
// ... (other values)
}), [masterPassphrase /* ... */]);
return (
<VaultSessionContext.Provider value={value}>
{children}
</VaultSessionContext.Provider>
);
};
export const useVaultSession = (): VaultSessionContextType => {
// ...
};
Client-Side Encryption
The keysafe.ts module handles all cryptographic operations directly in your browser using the standard Web Crypto API. It derives a key from your passphrase and uses AES-256-GCM to encrypt and decrypt your API keys.
'use client';
// ... (type definitions)
const ALGO_NAME = 'AES-GCM';
const PBKDF2_ITERATIONS = 100000;
async function deriveKey(passphrase: string, salt: Uint8Array): Promise<CryptoKey> {
const keyMaterial = await crypto.subtle.importKey(/* ... */);
return crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt,
iterations: PBKDF2_ITERATIONS,
hash: 'SHA-256',
},
keyMaterial,
{ name: ALGO_NAME, length: 256 },
true,
['encrypt', 'decrypt']
);
}
export async function encryptKey(passphrase: string, plaintext: string): Promise<EncryptedBlob> {
const salt = crypto.getRandomValues(new Uint8Array(16));
const iv = crypto.getRandomValues(new Uint8Array(12));
const key = await deriveKey(passphrase, salt);
const ciphertext = await crypto.subtle.encrypt({ name: ALGO_NAME, iv }, key, /* ... */);
return { iv: /* ... */, salt: /* ... */, ciphertext: /* ... */ };
}
export async function decryptKey(passphrase: string, blob: EncryptedBlob): Promise<string> {
const salt = base64ToBuffer(blob.salt);
const iv = base64ToBuffer(blob.iv);
const ciphertext = base64ToBuffer(blob.ciphertext);
const key = await deriveKey(passphrase, new Uint8Array(salt));
const decrypted = await crypto.subtle.decrypt({ name: ALGO_NAME, iv }, key, ciphertext);
return new TextDecoder().decode(decrypted);
}
Local Storage in Browser
Encrypted data is stored locally on your device using IndexedDB, a standard browser database. The vault.ts module manages this storage, ensuring that only encrypted blobs are ever written or read.
'use client';
import { openDB, type DBSchema, type IDBPDatabase } from 'idb';
import type { EncryptedBlob } from './keysafe';
const DB_NAME = 'branch-vault-2';
const STORE_NAME = 'encrypted-keys';
// ... (database setup)
export async function saveCipherBlob(provider: string, blob: EncryptedBlob): Promise<void> {
const db = await getDb();
await db.put(STORE_NAME, blob, provider);
}
export async function getCipherBlob(provider: string): Promise<EncryptedBlob | undefined> {
const db = await getDb();
return db.get(STORE_NAME, provider);
}
export async function deleteCipherBlob(provider: string): Promise<void> {
// ...
}
Server-Side Key Validation
To confirm your key works, we make a minimal, non-destructive API call from the server to the AI provider (e.g., asking "ping"). This validation happens *after* you decrypt the key in your browser and securely send it for this single request. We never log or store the key on the server.
We never send your Master Passphrase or plaintext API keys to our servers. The architecture is intentionally “zero-knowledge”: our backend never has the information needed to decrypt your keys.
Open Source & Transparency
We believe in transparency. You don't have to just trust us—you can verify our security claims yourself.
Threat Model & FAQ
Common questions about our security model.