> ## Documentation Index
> Fetch the complete documentation index at: https://pro-bot.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Managed key storage

> How ProBot's optional server-side LLM-key storage works: envelope encryption with a KEK that never lives in the database.

Your LLM API key is stored server-side with **envelope encryption** so recruiters can chat with your bot at any time, even when your browser is closed. This page explains exactly what "envelope-encrypted" means and what a database leak would (and wouldn't) reveal.

## Envelope encryption

Rather than a single static secret, ProBot uses two keys stacked:

1. A **KEK** (Key Encryption Key) - a 32-byte symmetric key - lives only in the `PROBOT_KEY_ENCRYPTION_KEY` environment variable. Never in the database, never in git, never in a DB backup.
2. A fresh **DEK** (Data Encryption Key) is generated per bot. The DEK encrypts your LLM key with AES-256-GCM.
3. The DEK is itself encrypted ("wrapped") with the KEK and stored next to the ciphertext.
4. At chat time the server loads the ciphertext + wrapped DEK, unwraps the DEK with the KEK, decrypts your key, calls the provider, and discards everything from memory.

```mermaid theme={null}
flowchart LR
    Key["Your LLM key"] -->|AES-256-GCM| CT["Ciphertext"]
    DEK["DEK (per bot)"] --> CT
    KEK["KEK (env var only)"] -->|wraps| WDEK["Wrapped DEK"]
    DEK --> WDEK
    CT --> DB[("Database<br/>ciphertext + wrapped DEK")]
    WDEK --> DB
```

## Why this matters

A database dump, a SQL-injection read, or a stolen backup yields only ciphertext and a wrapped DEK - both useless without the KEK, which isn't in the database. To rotate, change the KEK and re-wrap; the underlying LLM key never has to be re-entered if you script the re-wrap.

When you **self-host**, you own the KEK, so even the managed operator cannot decrypt your stored key. See [Managed vs self-hosted](/concepts/managed-vs-self-hosted).
