Encryption at rest¶
By default, each account's persistent profile — which holds real session cookies — lives on disk as plaintext Firefox profile files. Anyone with read access to ~/.ghostvault/profiles/ can steal your sessions. Encryption at rest stores those profiles as AES-256-GCM archives instead, with the key in your OS keychain.
Why this matters¶
The session cookies in a profile directory are effectively a password to your account. If someone gets a copy of those files — laptop theft, a stolen backup, a misconfigured cloud sync, a shared workstation — they can replay the session and bypass 2FA. That's true of any browser profile, not just Ghostvault's.
Encryption at rest closes that hole. When enabled:
- The profile directory is stored as
<account_id>.profile.enc— an authenticated AES-256-GCM ciphertext. - The master key lives in your OS keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service), never on disk.
- The profile is decrypted to a temp directory only while the browser is running, and re-encrypted on close.
What this protects against — and what it doesn't
Encryption at rest protects against disk theft (someone gets a copy of the files). It does not protect against an attacker who compromises the running browser session — while Ghostvault has the profile open, it exists as plaintext in a temp directory. For that threat, see Private/public profiles and the auto-close/auto-lock settings below.
How it works¶
Profile closed (at rest)
┌──────────────────────────────────────────────┐
│ ~/.ghostvault/profiles/ │
│ <account_id>.profile.enc ← AES-256-GCM │ disk: ciphertext only
│ <account_id>.fingerprint.json (plaintext) │ identity, no secrets
│ OS keychain: ghostvault master key │ key never on disk
└──────────────────────────────────────────────┘
│
│ gv_open_account
▼
Profile open (running)
┌──────────────────────────────────────────────┐
│ /tmp/ghostvault-XXXX/ ← decrypted tempdir │ plaintext, this window only
│ cookies, localStorage, ... │
└──────────────────────────────────────────────┘
│
│ gv_close_account / gv_lock_account
▼
Re-encrypted → .profile.enc, tempdir wiped
The lifecycle, in words:
- Browser opens (
gv_open_account) — the.profile.encarchive is decrypted to a fresh temp directory using the keychain master key. The browser runs against that tempdir. - Browser closes (
gv_close_accountorgv_lock_account) — the tempdir is re-encrypted to.profile.encand the tempdir is wiped. - First open after enabling — any existing plaintext profile is auto-migrated into an encrypted archive. You don't have to re-login.
Enabling it¶
It's a single env var, off by default:
Or set it in your client's env block (Claude Desktop, Cursor, etc.):
{
"mcpServers": {
"ghostvault": {
"command": "/absolute/path/to/python",
"args": ["-m", "ghostvault"],
"env": {
"GHOSTVAULT_ENCRYPTION_ENABLED": "true"
}
}
}
}
Restart your client after changing the env block. On the first gv_open_account after enabling, Ghostvault detects the plaintext profile and migrates it to an encrypted archive — your existing sessions survive.
Verify it's actually on
After enabling, check that profiles are now stored as .profile.enc files (not plaintext directories) under ~/.ghostvault/profiles/. If you still see plaintext profiles/<account_id>/ directories after opening + closing an account, encryption didn't engage — usually because the keychain backend is missing (see caveats below).
Requirements & caveats¶
Keychain backend required¶
The master key has to live somewhere. Ghostvault uses your OS keychain:
| OS | Backend |
|---|---|
| macOS | Keychain |
| Windows | Credential Manager |
| Linux | Secret Service (GNOME Keyring / KWallet) via D-Bus |
On headless Linux without D-Bus / Secret Service — for example, a bare Docker container, or a server without a desktop environment — there's nowhere secure to store the key. In that case encryption stays disabled even if you set the env var. Run gnome-keyring or kwallet first.
# Debian/Ubuntu headless server example
sudo apt install gnome-keyring
# Start the D-Bus session + unlock the keyring at login
Losing the key = losing the profiles¶
The master key is auto-generated on first use and stored in the keychain. It is not derivable from anything else — not your OS password, not your Ghostvault config, not the ciphertext. There is no recovery path by design; that's what makes it secure.
If the keychain entry is deleted (you wiped the keychain, reinstalled the OS, moved to a new machine without migrating the key), the encrypted profiles are gone. You'd need to re-login every account from scratch.
Back up the key if you back up the profiles
If you back up ~/.ghostvault/profiles/*.profile.enc to another machine or cloud storage, those backups are useless without the master key. Export the key from the source keychain and store it (and only it) somewhere separate. Or — simpler — don't back up the encrypted blobs; plan to re-login on a restored machine.
The browser still sees plaintext while running¶
Encryption is for data at rest. While the browser is open, the decrypted profile exists in a temp directory. An attacker who can read your filesystem during a running session — malware, a malicious process, a forensic capture — gets the plaintext. Mitigations:
- Close the browser when you're done —
gv_close_accountre-encrypts and wipes the tempdir immediately. - Enable auto-close (
GHOSTVAULT_AUTO_CLOSE_AFTER_TASK=true) to re-encrypt after every tool call. Costs ~1–2s per call to re-open; minimizes the exposure window. - Enable auto-lock (
GHOSTVAULT_AUTO_LOCK_MINUTES=N) to close idle contexts after N minutes. Belt-and-suspenders for when you forget to close.
Fingerprint files stay plaintext¶
The .fingerprint.json sidecar (which describes the spoofed identity — UA, screen size, locale, noise seeds) is not encrypted. It contains no session secrets, only identity metadata, so encrypting it isn't worth the complexity. Only the profile directory (cookies, localStorage, the actual session) gets the AES-256-GCM treatment.
Defense in depth: auto-close + auto-lock¶
For sensitive setups, combine encryption with the exposure-minimizing settings. The goal is to shrink the window during which a decrypted profile exists on disk.
| Setting | Default | What it does |
|---|---|---|
GHOSTVAULT_ENCRYPTION_ENABLED |
false |
Store profiles as encrypted archives |
GHOSTVAULT_AUTO_CLOSE_AFTER_TASK |
false |
Close + re-encrypt after every tool call (~1–2s reopen cost per call) |
GHOSTVAULT_AUTO_LOCK_MINUTES |
0 (disabled) |
Close idle contexts after N minutes of no tool calls |
A paranoid-but-practical config for a sensitive account:
"env": {
"GHOSTVAULT_ENCRYPTION_ENABLED": "true",
"GHOSTVAULT_AUTO_CLOSE_AFTER_TASK": "false",
"GHOSTVAULT_AUTO_LOCK_MINUTES": "15"
}
This keeps profiles encrypted at rest, and closes any browser context that's been idle for 15 minutes — so forgetting to close doesn't leave a decrypted profile sitting around all day. Leave AUTO_CLOSE_AFTER_TASK=false unless you're on an actively hostile machine; the per-call reopen cost adds up.
The gv_lock_account tool does both jobs in one call — it closes the browser context and re-encrypts the profile. Use it instead of gv_close_account when encryption is on:
# Encryption on: prefer gv_lock_account (close + re-encrypt in one call)
await gv.lock_account("acc_work123")
How this differs from the private/public gate¶
These two features are easy to conflate. They protect against different threats and compose cleanly — use both for sensitive accounts.
| Encryption at rest | Private/public gate | |
|---|---|---|
| Threat | Disk theft (laptop stolen, backup leaked) | Someone uses the agent on your unlocked machine |
| What it protects | The profile files on disk | Visibility/accessibility via the agent |
| Mechanism | AES-256-GCM archive + OS keychain key | Password gate; private profiles hidden when locked |
| Failure mode | Lose keychain entry → lose all encrypted profiles | 5 wrong guesses → wipe private profiles |
| Default | Off | Off |
| See | This guide | Private/public profiles |
The threat models don't overlap: encryption doesn't help if the attacker is sitting at your unlocked keyboard using the agent; the gate doesn't help if the attacker has a copy of the disk. For high-value accounts (banking, payments, primary email), enable both.
Troubleshooting¶
| Symptom | Likely cause | Fix |
|---|---|---|
| Set env var but profiles still plaintext | Keychain backend missing (headless Linux) | Install + start gnome-keyring (or kwallet); verify with a Secret Service client |
gv_open_account fails after enabling |
Keychain entry was deleted; key is gone | No recovery — re-login the affected accounts |
| Migration didn't happen | First open after enabling triggers migration; you may not have opened the account yet | Open then close the account once; check for .profile.enc |
| Slow tool calls after enabling | AUTO_CLOSE_AFTER_TASK=true re-encrypts after every call |
Set it to false; rely on AUTO_LOCK_MINUTES instead |
.fingerprint.json is still readable |
By design — fingerprint sidecars are plaintext (no session secrets) | Nothing to fix |
| Moved to a new machine, can't open profiles | The master key didn't come with you | Re-login on the new machine. Plan for this. |
Related guides¶
- Private/public profiles — the complementary access-control layer; use both for sensitive accounts
- Reading Gmail — sessions are sensitive; encryption protects them at rest
- Advanced → Environment variables — full list of
GHOSTVAULT_*settings