API key vault
The vault is a recovery surface, not a permission surface. When an org turns it on, every newly issued API key, personal access token, and provider credential is encrypted at rest with AES-256-GCM alongside the existing bcrypt hash. The hash still gates request authentication; the vault lets an owner or admin recover the plaintext if the human who issued the key lost it.
Reveal authority equals key-create authority. The vault does not introduce a new admin role. An admin who could have issued the key in the first place can also reveal a previously-issued key after stepping up their own credentials.
When to enable the vault
Turn it on when at least one of the following is true:
- You issue keys on behalf of teammates who do not have IQ Routing accounts (consultants, contractors, agencies). Without the vault, a rotated or lost key forces a new issue and a new wiring step in their application; with the vault, you reveal once and resend.
- Your org keeps key plaintext in 1Password / Bitwarden / a password manager and the manager occasionally loses sync. The vault is the recovery path of last resort.
- Your security review requires that key plaintext is recoverable for forensic purposes (incident response, audit trail correlation).
Leave it off when the operational pattern is "the human who created the key keeps the plaintext; if they lose it, they re-issue." That is the default flow and it remains supported.
How to enable
Settings → Key vault → toggle the switch. The change applies to all keys, tokens, and provider credentials issued after the toggle flips. Existing rows stay unvaulted; the next rotation will encrypt them into the vault.
The dashboard surface lives at /settings#key-vault.
How a reveal works
- Open Keys → click the row → click "Reveal".
- The dashboard prompts for step-up authentication. Either enter your account password or complete a WebAuthn challenge if your account has a security key registered.
- The reveal endpoint decrypts the AES-GCM blob server-side, returns the plaintext, and starts a 30-second display window. A countdown ticks down on screen; the plaintext clears from the DOM when it reaches zero.
- Every reveal writes an
api_key_revealedrow to the audit log with the actor email, the key id, and the step-up method. The audit row surfaces in Audit under the actor's filter chip. - Every reveal also enqueues a webhook delivery to any active
destination subscribed to the
api_key_revealedevent so external security tooling (a SIEM, a Slack channel) can react in real time.
Limits and rate limits
- 10 reveals per user per hour. A token bucket on the gateway side
rejects further reveals with
429 Too Many Requests. The bucket resets at the top of the hour. - 30-second TTL on the plaintext. No way to extend; copy the plaintext to your password manager during the window or re-issue.
- One step-up per reveal. Step-up does not grant a session-wide trust window; every reveal re-prompts.
- Vaulted rows only. Keys issued before the vault toggle flipped
are unvaulted; their reveal endpoint returns
404 Not Found. Rotate them to encrypt forward.
Encryption details
- Cipher. AES-256-GCM with a 96-bit nonce drawn from
secrets.token_bytes(12)per encrypt. Authenticated; tampering with the ciphertext at rest causes decrypt to fail rather than return garbage. - Key material. A single master key is loaded from the
KEY_VAULT_MASTER_KEY_V1environment variable on the hosted gateway, or from the platform keychain on the desktop bundle. The master key never appears in the database or in logs. - Versioning. The encrypt header carries a one-byte version. The
current version is
1. A future rotation lands av2master key and marks new rows with version2; the decrypt path dispatches on the header byte so a mixed-version table stays readable through the rotation window. - No client-side decrypt. The encrypted blob never leaves the gateway. The dashboard receives only the plaintext during the bounded reveal window.
Rollback
If the vault behaves badly in production, set the env var
KEY_VAULT_ENABLED=false globally. The issue path skips encryption,
the reveal endpoint returns 503 with a "vault temporarily disabled"
message, and existing vaulted rows stay encrypted (reveal returns once
the env flips back). The bcrypt verification path is unaffected so
request authentication keeps working through the rollback.
See also
- /docs/auth -- how API keys, scopes, and the
gw_live_prefix work end-to-end. - /docs/webhooks -- wiring the
api_key_revealedevent into an external destination. - /audit -- the audit log surface where reveal rows land.
IP allowlist
The reveal endpoint enforces step-up auth, the per-user 10/hour rate limit, the active-org membership check, and a mandatory audit row. A per-org IP allowlist sits on top.
Set the allowlist in Settings → Key vault → IP allowlist. The
textarea accepts one CIDR per line (203.0.113.0/24, 2001:db8::/32,
or a single host like 203.0.113.42/32). An empty list disables the
gate; a non-empty list rejects any reveal whose source IP does not
match. Rejected attempts write an api_key_reveal_ip_blocked audit
row carrying the resolved source IP and the size of the allowlist.
The gateway honours X-Forwarded-For first (the leftmost entry, set
by Fly's edge) and falls back to the TCP source IP. A direct-deploy
host without an edge proxy in front of it should set
IQ_TRUST_X_FORWARDED_FOR=false so a spoofed header cannot bypass
the gate.
The allowlist is per-org, not per-user. An operator who works from multiple machines lists every CIDR; the allowlist is defence in depth on top of step-up auth, not a per-machine identity.
WebAuthn-required reveals
The default reveal flow accepts the account password step-up or a WebAuthn assertion. A session token plus a phished password is enough to reveal under the default policy.
For orgs whose compliance posture requires hardware-backed step-up,
flip Settings → Key vault → Require passkey for reveals. The
toggle rejects password-only step-up with 403 WebAuthn registration required. The operator must register a passkey under
Settings → Key vault first.
The toggle is independent of the vault enable/disable toggle; an org can enable vaulting and password reveals together during an initial trial, then promote to passkey-required once every operator has enrolled. A passkey loss does not lock the operator out of the dashboard -- only the reveal endpoint gates on the passkey, so the operator can still log in normally, disable the toggle, and rotate keys via the existing revoke-and-reissue flow.