Secrets in API Sandbox never leave the browser. Sensitive values are wrapped in an authenticated envelope before they are persisted to IndexedDB.
Each secret row stores { meta, name, environmentId?, envelope } where envelope is:
interface SecretEnvelope {
v: 1; // schema version
alg: "AES-GCM"; // authenticated cipher
salt: string; // base64url PBKDF2 salt (16 bytes)
iv: string; // base64url AES-GCM IV (12 bytes)
ct: string; // base64url ciphertext
}All envelope fields use base64url so they stay filename/JSON friendly.
- KDF: PBKDF2 with SHA‑256, 200,000 iterations, 16‑byte salt.
- Cipher: AES‑GCM with 256‑bit keys and a random 12‑byte IV per encryption.
- Plaintext: Never stored alongside the envelope; the IndexedDB row contains metadata + ciphertext only.
SecretCryptoService keeps the derived passphrase key in memory only:
- Unlocking imports the passphrase through WebCrypto and holds the base key in RAM.
- Encrypt/decrypt helpers derive per‑secret keys using the stored base key and the envelope's salt.
- Locking drops the in‑memory key.
- A
beforeunloadlistener automatically locks when the tab refreshes or closes.
Because there is no persisted verifier, the UI attempts to decrypt a stored envelope to validate the passphrase and surfaces a gentle error when it fails.
To rotate secrets:
- Unlock with the current passphrase.
- Reveal or export the environment variables that contain
{{$secret.*}}placeholders. - Lock, then unlock with the new passphrase.
- Re‑encrypt each sensitive value (Protect Variable → new ciphertext).
- Optionally delete stale secrets via the IndexedDB
secretsstore (future UI).
At no point does the app send secrets to a backend—everything stays local to the browser profile.