ADR-0035 — Machine identity: service accounts + scoped, hashed API keys
- Status: Deferred
- Date: 2026-06-11
- Related: security/02 identity, security/08 secrets, ADR-0010, ADR-0030
V1 Freeze (2026-06-12): Deferred. No workload-identity fleet in V1. Re-opens at P4.
Context
BitVault must authenticate non-human callers — automation, CLIs, third-party integrations, and “BitVault-as-a-backend” apps. Doing this with shared user passwords or long-lived god-tokens is a common source of breaches (leaked keys, no rotation, no attribution, over-broad scope). We need first-class machine identity with least privilege and auditability.
Decision
- Service accounts are first-class machine principals owned by an org/team, with roles/ scopes but no interactive login, no MFA, no human session.
- They authenticate via (a) workload OIDC (short-lived JWT/mTLS from the runtime — no static secret; preferred) or (b) scoped API keys.
- API keys: format
bv_<env>_<keyid>_<secret>(public prefix/keyid + ≥256-bit secret); stored hashed (keyid indexes the row, secret verified by hash + constant- time compare); shown once; scoped to actions + resource prefixes (a subset of the principal’s rights); expirable, rotatable (overlap), revocable; last-used tracked. - API keys carry no interactive privileges — destructive/admin actions need step-up (ADR-0036). Leak defense: secret scanning in CI (ADR-0032).
Consequences
Positive
- Least-privilege, attributable, rotatable machine access; a DB dump yields no usable keys (hashing); a leaked key has a small, time-boxed blast radius.
- Workload OIDC removes static secrets entirely where supported.
Negative / costs
- Key lifecycle (rotation/revocation/overlap) + scoping UX to build and operate.
- Users must treat one-time key display correctly (no recovery by design).
Alternatives considered
- Long-lived personal tokens shared as “the API password”: no scope, no rotation, no attribution — the anti-pattern we exist to avoid. Rejected.
- Store keys reversibly (encrypted, not hashed): enables display/recovery but a key-store compromise leaks all keys. Rejected — hash, show once.
- OAuth client-credentials only (no API keys): cleaner but higher friction for simple scripts/integrations; we offer both (keys for simplicity, workload OIDC for rigor).
Scaling
Keyid-indexed hashed lookup is O(1); scopes evaluated by the PDP (ADR-0010); per-tenant key namespaces; stale-key reaping via last-used.