What QuoxVault is #
Most agent stacks hand the model an API key and hope. QuoxVault takes the opposite position: secrets stay in the vault, and agents are granted leases, time-boxed, use-counted permissions to have a secret used on their behalf.
The credential is resolved and consumed server-side by a typed executor (send an email, run an SSH command, query a database), and the plaintext never appears in the agent's context, its logs or its reply.
Three ideas carry the design:
- Typed storage. A secret is not just a string. Each credential kind carries shape validation and its own release policy.
- Leased release. Access is a lease with an expiry and an optional use count, requested by an agent and approvable by a human.
- Witnessed access. Every release emits an audit event witnessed onto the WARD hash chain, so the record of who accessed what is tamper-evident.
QuoxVault ships today as a capability inside QuoxCORE's auth service. A standalone vault product is planned and nothing of it has shipped; see stable, beta and planned.

Credential kinds Stable #
The taxonomy defines eight credential kinds. Each kind declares the shape a credential must have and the release policy that governs how it may leave the vault. Shape validation runs on the decrypted fields, and there is deliberately no database constraint on the kind column, so a new kind can ship without a migration.
| Kind | Holds | Consumer today | Maturity |
|---|---|---|---|
service_api | Provider API keys | Resolver waterfall: bring-your-own-key, then org, instance and environment keys | Stable |
email_account | IMAP and SMTP accounts | email_read and email_send tools. Username and password only; OAuth is not shipped | Beta |
web_login | Site logins, TOTP seeds | Governed browser login; TOTP codes are computed server-side and the seed never leaves the server | Beta |
ssh_key | SSH private keys | remote_ssh: key used in memory only, host keys pinned on first use, drift hard-fails | Beta |
tls_cert | Certificates and keys | tls_cert_info: X.509 parse, expiry and metadata | Beta |
database | Database credentials | db_query: read-only by default, writes need an explicit flag | Beta |
cloud | AWS, GCP, Azure credentials | None. Stores and decrypts correctly, but no executor opens a cloud session yet | Stub |
generic | Anything else | Storage and governed release only | Stable |
Alongside the eight kinds, encrypted session state (cookies and session tokens for browser automation) has its own store with a 12 hour staleness contract, replacing an earlier plaintext cookie-file design. Stable
NoteGmail, Outlook and Microsoft 365 OAuth do not exist in QuoxVault today. The accepted credential shape reserves fields for OAuth tokens, but there is no consent flow or refresh-token exchange. IMAP and SMTP with a username and password is the only working email path.
Envelope encryption Stable #
Every credential is sealed in its own envelope. A key encryption key (KEK) is derived from the master key with HKDF; each credential gets its own data encryption key (DEK), which encrypts the secret with AES-256-GCM and is itself wrapped by the KEK.
The GCM authentication tag means any bit flip in the ciphertext or the wrapped DEK makes decryption throw rather than return corrupt plaintext. Legacy rows in the older three-part format still decrypt through the same router.
# two formats, one decrypt router (dispatch on the v2: prefix)
legacy iv:authTag:ciphertext # v1 rows still decrypt
v2 v2:<envelope> # per-credential DEK, wrapped by the KEKRotation is a re-wrap
Rotating the master key re-wraps only the DEKs, not the ciphertexts. SettingMASTER_ENCRYPTION_KEY_PREVIOUS opens a dual-key window in which rows wrapped under the previous key still decrypt, so rotation cannot lock an operator out.
Coverage by construction
A registry lists every table that holds vault ciphertext (six today). A CI test fails if a new ciphertext column ships without being registered, so rotation and key validation cannot silently miss a store. This closed a real incident class: an earlier rotation missed a table because coverage relied on discipline rather than a failing test.
Known gapThe KEK is global, not per organisation. Tenant isolation of encrypted data is an application-layer property (org-scoped routes and queries), not a cryptographic one: anyone holding the master key and the salt can unwrap every organisation's DEKs. This is a recorded decision (2026-07-11), accepted while the platform is effectively single-tenant, to be revisited when a second real tenant lands or the standalone vault is scheduled.
NoteDEK buffers are not yet zeroed after use, a cheap follow-up that has not landed. Decrypted values held as JavaScript strings cannot be scrubbed from memory at all; that is a Node and V8 platform limitation, accepted and documented rather than claimed away.
Dynamic leasing Beta #
A lease binds one agent to one credential for a bounded window: an expiry, and optionally a maximum number of uses. The lease lifecycle (create, approve, revoke, expire on a 30 second tick) is Stable and predates the current audit.
The enforcement wiring that makes live release paths respect leases is Beta: it was live-verified on 2026-07-11, with enforced mode returning 403 without a lease and a five-use lease allowing exactly five releases.
Enforcement is one fail-closed check in a single SQL predicate, so the three conditions cannot drift apart, and use counting is atomic. A lease held by the wrong agent is a WHERE-clause miss, not a comparison that could be bypassed.
-- a required lease releases only when all three hold, in one predicate
status = 'active'
AND expires_at > now()
AND (max_uses IS NULL OR use_count < max_uses)Enforcement modes
Each organisation carries a three-state flag: off, advisory orenforced. The default is advisory, a deliberate rollout choice, and a malformed setting fails safe rather than silently becoming enforced.
WarningIn advisory mode, the default for every organisation, a caller that omits a lease still receives the secret. The audit trail is the only defence: detective, not preventive. There is currently no dedicated UI or API to flip an organisation to enforced; the only write path is the generic organisation settings endpoint.
And off drops the lease audit events as well as enforcement, a silent double loss. If you rely on leases as a control, set the mode to enforced and verify it.
Human approval
A pending lease request creates an item in the HITL inbox instead of timing out silently. Approving the item resolves the lease. Live-verified 2026-07-11.Beta
Tamper-evident audit Beta #
Every secret release path emits a secret.access audit event and witnesses it onto the WARD hash chain. Verifying the chain detects mutation of any witnessed entry: change a row and the hash linkage breaks, and verification reports it.
Honesty requires the history here. The 2026-07 adversarial audit found that an intent-prefix mismatch had caused every witness call since the integration shipped to silently write zero chain entries; the call never threw, and the only existing test checked that it did not throw.
The bug was fixed in the same audit and 15 chain entries were live-verified afterwards. The audit also found that the witnessed hash did not commit to the actor, target and organisation fields of the underlying event; the stream's decisions log records that gap closed later in the same audit with a reconciliation check and release audit on all resolve paths.
- Chain tamper detection: mutating a chain row is detected and reported as broken. Stable, narrow scope.
- Witnessing on secret access: wired on all release paths, live-verified after the fix above. Beta
Known gapPlugin backend credential injection bypasses the resolver, the lease gate and the audit chain entirely: the supervisor decrypts and injects plaintext as container environment variables, visible to anyone who can inspect the container. Fixing it breaks the plugin SDK credential contract, so it is deferred by recorded decision to a tracked stream rather than patched quietly. Treat plugin-injected credentials as outside the vault's guarantees today.
Cross-org isolation Stable #
Every internal release endpoint checks the caller's organisation against the credential's, and mismatches return 404 rather than 403 where an existence oracle would leak that a credential id is real. This isolation is application-layer; the cryptographic caveat is the global KEK described under envelope encryption.
| Internal release path | Org check |
|---|---|
integrations/:id/decrypt | Mandatory, 403 on mismatch |
integrations/:id/service-decrypt | Mandatory, 404 on mismatch, no existence oracle |
integrations/by-domain/:domain | Mandatory, queries scoped to the org |
credential-decrypt/:id | Mandatory, 404 on mismatch |
credential-totp/:id | Mandatory, 404 on mismatch |
resolve-for-agent | Org and agent scope the full resolution chain |
resolve-by-id | Org id required in all environments; requests without it are rejected |
The resolve-by-id path was the last one to reach mandatory enforcement: for a period it verified the org id only when supplied, and requiring it in production waited on one caller being migrated to thread the org id through. That migration landed and the requirement is now on everywhere; only an explicit opt-out environment variable disables it.
Stable, beta and planned #
This section mirrors the surface's status and limits document, the honest maturity map kept with the code and updated at every audit. If the two ever disagree, the document in the repository wins.
Stable Verified and pinned by regression tests
- v2 envelope encryption (HKDF KEK, per-credential DEK, AES-256-GCM), with legacy rows decrypting through the same router
- Key rotation as a DEK re-wrap with a dual-key window
- Encrypted-store completeness enforced by a failing CI test
- Boot-time master key validation and the periodic key health scan
- The typed credential taxonomy and its shape validation
- Encrypted session state with a 12 hour staleness contract
- Lease lifecycle: create, approve, revoke, expiry job
- Org checks on every internal release endpoint
- WARD chain tamper detection over witnessed entries
Beta Working, live-verified once, still hardening
- Email consumer (IMAP read, SMTP send)
- Web login consumer with server-side TOTP
- SSH consumer with trust-on-first-use host key pinning
- TLS certificate parsing and expiry metadata
- Database query consumer, read-only by default
- Lease enforcement wiring and the three-state org mode
- HITL inbox approval for lease requests
- WARD witnessing of every secret access
- Typed field widgets in the credential UI
Planned Designed or reserved, nothing shipped
- Gmail, Outlook and Microsoft 365 OAuth email credentials
- Executors for the
cloudcredential kind - An egress guard on vault-fed host fields, to be wired when those executors ship
- The standalone QuoxVault product outside QuoxCORE
Known gap Documented limitations, not softened
- Plugin backend credential injection bypasses the resolver, lease gate and audit chain; deferred by recorded decision to a tracked stream
- The KEK is global rather than per organisation; encrypted-at-rest tenant isolation is application-layer only
- Lease enforcement defaults to
advisory, and no dedicated UI or API flips an organisation toenforced offmode drops lease audit events along with enforcement- DEK buffers are not zeroed after use, and decrypted strings cannot be scrubbed in Node
- Backup bundles contain vault ciphertexts; a backup plus the master key decrypts everything in it (accepted and documented)
- The suite is not yet self-red-teamed against a live deployment; container log sweeps and a staging key rotation drill remain open
Test coverage #
The vault's claims are backed by an adversarial test corpus that runs against real database rows and real service boundaries, not mocks of the vault itself. Roughly 140 tests across eight files:
| Corpus | Tests | Covers |
|---|---|---|
| Adversarial suite | 54 | Cross-org isolation on every release path, forged service keys, lease abuse (expired, revoked, over-limit, wrong agent), chain tampering, envelope round-trips, TOTP vector correctness |
| Log-leak greps | 23 | No passwords, private keys or PEM material in captured logs across representative flows |
| Integration encryption boundary | 22 | Encryption behaviour at service boundaries |
| Envelope unit tests | 11 | Round-trip, bit-flip tamper evidence, legacy dispatch |
| Credential decrypt regression | 10 | The decrypt endpoint returns plaintext, not echoed ciphertext |
| Collector field encryption | 10 | Collector-side encryption boundaries |
| Security pins | 7 | Closed findings pinned green as regression guards |
| Store completeness | 3 | CI fails on an unregistered ciphertext column |
Stated plainly, what this corpus does not verify: a live red-team exercise against a running deployment, end-to-end container log sweeps across the full stack, a staging rehearsal of master key rotation, and real IMAP or SSH endpoints (transports are mocked).
Four tests are explicitly marked as findings rather than passes: they assert that hostile server-shaped values are stored and returned unchanged, not that a guard exists, because none does yet.
Operating notes #
The settings an operator actually touches:
MASTER_ENCRYPTION_KEY=<32-byte base64> # required; boot fails loudly without it
MASTER_ENCRYPTION_KEY_PREVIOUS= # set only during a rotation window
RESOLVE_BY_ID_REQUIRE_ORG= # unset means enforced; 'false' opts out| Setting | Default | Effect |
|---|---|---|
MASTER_ENCRYPTION_KEY | none | The raw master key. Validated at boot; an empty value fails loudly at the application layer |
MASTER_ENCRYPTION_KEY_PREVIOUS | unset | Opens the dual-key rotation window. Leaving it set indefinitely keeps a leaked previous key as a live decrypt path |
vault.lease_enforcement (per org) | advisory | off, advisory or enforced. Fails safe on a malformed value |
RESOLVE_BY_ID_REQUIRE_ORG | unset, meaning enforced | Org id is required on resolve-by-id in all environments; only an explicit false disables the check |
WarningCompose files interpolate an unset MASTER_ENCRYPTION_KEY to an empty string without complaint; the boot-time check is what catches it. Do not remove that check, and do not run the vault with enforcement assumptions you have not verified against your organisation's actual mode.