Skip to content
IQ Routing

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

  1. Open Keys → click the row → click "Reveal".
  2. 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.
  3. 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.
  4. Every reveal writes an api_key_revealed row 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.
  5. Every reveal also enqueues a webhook delivery to any active destination subscribed to the api_key_revealed event 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_V1 environment 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 a v2 master key and marks new rows with version 2; 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_revealed event 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.