Encryption

This page describes the cryptography in the running product — not a future design. Banana Peel is SOC 2 Type II; the controls below are what that report covers in engineering terms. Ciphertext formats are versioned (v1 / v2) so upgrades roll forward without breaking older data.

Vault secrets — envelope encryption via Cloud KMS

Username, password, and optional TOTP seed are encrypted before they are written. Each secret gets its own fresh 256-bit data key (DEK); the payload is AES-256-GCM under that DEK (12-byte IV, 16-byte auth tag), and the DEK is wrapped by a dedicated Cloud KMS key (bp-vault, keyring bp-crypto, us-central1) with automatic 90-day rotation. Ciphertext is stored as v2:<kms_key>:<wrapped_dek>:<iv>:<tag>:<data>. KMS never sees secret payloads; unwrapped DEKs are never persisted (a bounded in-memory cache with a 10-minute TTL keeps hot reads off the KMS round-trip). Only the Cloud Run runtime service account holds cryptoKeyEncrypterDecrypter on that key — no human account does.

Legacy v1 ciphertext (AES-256-GCM under a static application key, SHA-256 of BP_VAULT_KEY) remains readable indefinitely; stored secrets were rolled forward to v2 with a one-shot idempotent migration, and every new write is v2. The legacy key stays configured as a decrypt fallback until the last v1 blob is gone.

List and get endpoints return metadata only (masked username, domain, custody). Ciphertext blobs are not selected into console lists. Decrypt happens in-process after the custody filter is proven satisfiable; a 409 custody_unsatisfiable request never decrypts the secret.

Run artifacts & screenshots — same envelope, separate key

Artifact bytes (downloaded bills, PDFs, CSVs, recordings) and the stored final-frame screenshot get the same per-object envelope: fresh DEK per object, AES-256-GCM payload, DEK wrapped by a separate KMS key (bp-artifacts, same keyring, same 90-day automatic rotation). This applies to artifact objects in the private GCS bucket, to inline Firestore fallback copies, and to the screenshot payload on the run document. Encrypted artifacts are never served via direct signed URLs — bytes stream through the authenticated artifact endpoint, which decrypts in-process after the owner check.

Objects written before envelope encryption shipped read as plaintext passthrough (versioned header detection) and age out under the retention window (7-day default) rather than being rewritten in place.

API keys — SHA-256

Customer keys (bp_live_…) are stored as a SHA-256 hash of the full secret plus a display prefix. The plaintext is returned once at creation. Agent claim tokens and webhook signing secrets use the v1 AES-256-GCM application-key construction.

In transit

The public API is HTTPS on Cloud Run. TLS terminates at the load balancer. There is no plaintext HTTP listener.

At rest — what gets a second layer and what deliberately does not

  • KMS envelope (v2), application layer: vault username / password / TOTP; artifact bytes (GCS objects, inline fallback copies); the stored final-frame screenshot.
  • App-key AES-256-GCM (v1), application layer: agent claim tokens, unclaimed API-key copies, webhook signing secrets.
  • Hashed: API keys, claim-token indexes.
  • Google-managed encryption at rest only (Firestore default): run document text fields — output, answer, steps, error strings, billing rows, benchmark aggregates. These fields feed grading, regrade, redaction, webhooks, list views, and analytics; wrapping them in an application cipher would break those read paths, so the honest control there is platform encryption plus a strict retention window — treat run output as sensitive operational data, not as a second vault.
  • Routing traces are redacted (passwords, tokens, JWTs, card-like digit runs, TOTP-shaped base32) before persist. Vault-injected secrets are never passed into the trace store; the decision card uses the pre-injection task text.

Zero-retention workspaces skip byte persistence entirely — no artifact or screenshot blobs are written at all, encrypted or otherwise.

See also

Command Palette

Search for a command to run...