This is the reference for security teams evaluating Versus Vault. It follows a secret from the moment it is written, through every way it is read — by a person, by a machine, by a login flow, by a device on a collector-only network — to how it is rotated, revealed under break-glass, and audited. It names the actual cryptographic primitives and protocols at each step, and it is candid about the trust boundaries: what the vault protects, and what protects the vault.
1. The key hierarchy
Versus Vault uses envelope encryption. A secret value is never encrypted directly under a shared key. Instead, each secret version is sealed with its own random 256-bit data encryption key (DEK) using AES-256-GCM (authenticated encryption with a fresh 96-bit nonce). That per-secret DEK is then wrapped — encrypted — under a master key-encryption key (KEK), again with AES-256-GCM.
The KEK is either an explicit 256-bit key supplied as VAULT_KEK, or is derived from the deployment's SECRET_KEY using PBKDF2-HMAC-SHA256 with 200,000 iterationsand a vault-specific salt. The sealed blob is self-describing (it carries a kek_ref tag), so the master key can be rotated by re-wrapping DEKs without ever re-encrypting ciphertext, and a future HSM-backed KEK can be introduced without a format change.
FIG 1 · KEY HIERARCHY
Every secret value is sealed with its own random 256-bit data key (DEK) under AES-256-GCM. The DEK is then wrapped by the master key-encryption key (KEK). The KEK is either an explicit VAULT_KEK or is derived from SECRET_KEY via PBKDF2-HMAC-SHA256 (200,000 iterations). Compromising one sealed value never exposes another; rotating the KEK re-wraps DEKs without touching ciphertext.
The security consequences of this design:
Blast-radius containment. Every secret has a unique DEK. Recovering the plaintext of one sealed value tells you nothing about any other.
Authenticated, not just confidential. AES-256-GCM detects tampering — a modified ciphertext fails to decrypt rather than returning garbage.
Key rotation without downtime. Rotating the KEK re-wraps the small DEKs, not the (potentially large) secret bodies.
2. Writing a secret
Writes go through one enforced service entry point. The caller's capability is asserted, the value is sealed with a fresh DEK, and a new version is written copy-on-write — the prior version is retained, never overwritten — and the operation is appended to the tamper-evident audit chain, all inside a single database transaction.
FIG 2 · WRITE (SEAL)
A write is policy-checked, sealed with a fresh per-secret DEK under AES-256-GCM, versioned copy-on-write (the prior version is retained, not overwritten), and appended to the tamper-evident audit chain — all in one transaction.
3. Reading a secret — a person
When a human reads a secret in the UI, the request passes three independent authorization gates before any key material is unwrapped:
Module entitlement — is the Secret Vault module licensed (on-prem) or included in the tenant's plan (SaaS)? If not, the request is refused with 402 before anything else runs.
RBAC level — the user's role level governs whether the vault is even visible (vault.view / vault.read).
Path policy capability — a per-path policy (glob → role → capabilities) must grant the read capability on that specific secret path.
Only after all three pass is the DEK unwrapped, the value decrypted, and the read written to the audit chain.
FIG 3 · HUMAN READ · TRIPLE GATE
A person reading a secret in the UI passes three independent gates before any decryption: (1) module entitlement (license/tenant — a 402 if the Vault module isn't licensed), (2) RBAC level for nav/visibility, (3) the per-path VaultPathPolicy capability ('read'). Only then is the DEK unwrapped, the value decrypted, and the read appended to the audit chain.
4. Reading a secret — a machine (service account)
A computer never uses a human's session. It authenticates as an app principal(Versus's service-account model) using the OAuth-style client-credentials pattern:
1 · Register
An admin creates an app principal with a set of capabilities and an allowed_paths glob. The raw client secret is shown exactly once; only its SHA-256 hash is stored.
2 · Exchange
POST /api/vault/token with {client_id, client_secret} → a short-lived Bearer token. The token is persisted by SHA-256 hash (it survives restarts and is shared across workers). The endpoint is rate-limited to blunt secret brute-forcing.
3 · Fetch
GET /api/vault/fetch?ref=vault://<mount>/<path>[&field=<k>] with Authorization: Bearer <token>. Authenticated ONLY by the app-principal token — a human JWT is not accepted here.
4 · Scope
The path is checked against a hard-deny list (devices/, clm/, snmp/, integrations/ are unreachable to any principal), the principal’s allowed_paths glob, and its capabilities. The read is audited to the principal, not a person.
FIG 4 · MACHINE / SERVICE-ACCOUNT READ
A computer authenticates as an app principal: it exchanges client_id + client_secret (throttled) for a short-lived bearer token, persisted by SHA-256 hash. It then calls GET /api/vault/fetch?ref=… with the bearer. The request is scoped by a hard-deny on protected prefixes (devices/, clm/, snmp/, integrations/), an allowed_paths glob, and the principal's capabilities — never a human's role — and is audited to the principal.
A minimal service-account read looks like this:
# 1. exchange client credentials for a short-lived bearer
TOKEN=$(curl -s https://versus.example.com/api/vault/token \
-H 'Content-Type: application/json' \
-d '{"client_id":"ci-runner","client_secret":"'"$CI_SECRET"'"}' \
| jq -r .access_token)
# 2. fetch a secret the principal is entitled to (optionally a single field)
curl -s "https://versus.example.com/api/vault/fetch?ref=vault://root/apps/ci/db&field=password" \
-H "Authorization: Bearer $TOKEN"
# → {"ref":"vault://root/apps/ci/db","value":"…","version":3}
NoteApp principals are deliberately weaker than people: they can read within their glob, but they can never check out a privileged account, request a break-glass reveal, or approve one. Those require a human identity.
5. The secret-type catalog
Not every secret lives in the same place, and that is deliberate. Application secrets live in the vault, where they are envelope-encrypted, versioned, policy-gated, and rotatable. TLS private keys use the certificate-lifecycle keystore's own AES-256-GCM envelope. A small number of bootstrap keys are process-level: they are what unlock the vault, so they cannot be stored inside it.
FIG 5 · SECRET-TYPE CATALOG
Every class of secret Versus holds, and where it lives. Application secrets live in the vault (envelope-encrypted, policy-gated, versioned, rotatable). TLS private keys use the CLM keystore's AES-256-GCM envelope. Bootstrap keys (the field/KEK master keys, DB URL) are process-level and are meant to be sourced from a KMS / sealed secrets and escrowed — they bootstrap the vault, so they cannot live in it.
The bootstrap keys — the field/KEK master keys, the database URL — are sourced from the environment and are meant to be delivered by a secrets manager (Kubernetes sealed-secrets, an external-secrets operator backed by Vault/KMS) and escrowed. Losing the field/KEK master key means encrypted data is unrecoverable, so it is treated as a disaster-recovery asset.
CautionVersus refuses to boot a production (non-debug) deployment that is still using the built-in default SECRET_KEY or FIELD_ENCRYPTION_KEY, unless explicitly overridden for a disposable environment. A default key would make encrypted data world-decryptable and session tokens forgeable — so it is a hard stop, not a warning.
6. Site-bound devices — the three collector vault modes
Many managed devices sit on networks Versus cannot reach directly; a lightweight collectorreaches them from inside. Device credentials for those targets are resolved on the collectorin one of three modes, chosen per collector, so a customer can decide exactly where device secrets live.
FIG 6 · COLLECTOR VAULT MODES
For site-bound devices reachable only through a collector, credentials are resolved on the collector in one of three modes. Mode A: a local file vault sealed with a key derived (HKDF) from the collector's own enrolled identity key — the value never leaves the customer network. Mode B: the cloud sends a per-task encrypted blob, cleared from memory after each task. Mode C: the collector fetches from the customer's own external vault (HashiCorp KV-v2). All collector transport is outbound bearer-authenticated TLS/WSS on 443 — a short-lived ES256 token signed with the collector's enrollment key.
Mode A — local. A file vault on the collector, sealed with AES-256-GCM under a key derived (HKDF-SHA256) from the collector's own enrolled identity key. The data key is never written to disk, and the secret never leaves the customer network.
Mode B — cloud-relayed. The control plane hands the collector a per-task encrypted blob, held only in memory and cleared after each task.
Mode C — external. The collector fetches from the customer's own HashiCorp Vault (KV-v2, AppRole auth); Versus stores only the lookup path, never the value.
All collector traffic is outbound TLS 1.2+, authenticated with a short-lived ES256 bearer token the collector signs with its enrollment key (a certificate-derived identity, not a client certificate) — there are no inbound firewall exceptions into the customer network.
7. How logins validate — the secrets behind authentication
Authentication is itself a consumer of secrets. Each backend uses a different one:
Local — the password is verified against a one-way PBKDF2-SHA256hash. The plaintext is never stored and never recoverable.
LDAP / Active Directory — a service account binds with an encrypted bind password (held in the auth backend's encrypted config, not plaintext), locates the user, then verifies the user's own password by re-binding as them.
OIDC / SAML — the relying party validates the identity provider's RS256-signed ID token against the IdP's published JWKS.
Every path then enforces TOTP MFA (RFC-6238) — the per-user authenticator seed is itself an encrypted secret — and mints a short-lived, rotating JWT for the single-page app.
FIG 7 · LOGIN FLOWS THAT CONSUME SECRETS
How a user login validates, and which secret each path uses. Local: password verified against a PBKDF2-SHA256 hash (never reversible). LDAP/AD: a service bind uses the encrypted bind password, then the user's own password is verified by re-bind. OIDC: the RP validates the IdP's RS256-signed ID token via JWKS. All paths then require TOTP MFA (RFC-6238) and mint a short-lived JWT for the SPA.
8. Privileged accounts — check-out & break-glass
Secrets marked privileged (device admin passwords, root accounts) get two extra controls. Exclusive check-out hands the credential to exactly one holder at a time (a database row-lock guarantees exclusivity) and auto-rotates it on check-in, so a password that was on someone's screen is dead the moment they return it. Break-glass reveal is dual-control: a reveal request must be approved by a different person, and the approval yields a single-use token valid for only minutes.
FIG 8 · PRIVILEGED CHECK-OUT & BREAK-GLASS
Privileged accounts support exclusive check-out (one holder at a time, auto-rotated on check-in so a disclosed password dies at return) and dual-control break-glass reveal (a request must be approved by a different person; the approval yields a one-time token valid for minutes). App principals are hard-denied both.
8a. Where a privileged account actually lives — and how the target validates it
A question every network and security reviewer asks: when someone checks a privileged password out of the vault and logs into a device, how does that device validate it against Versus? It doesn't — and it shouldn't. Versus Vault is a credential store (PAM), not the authentication authority for other systems. The device validates against whatever store already owns that account; Versus holds a synchronized copy and controls who may retrieve it. Where Versus syncs the credential therefore depends on where the account's authority is.
FIG 12 · WHERE A PRIVILEGED ACCOUNT LIVES
The sync target follows the account's authority. Case A: a local account on the target — Versus rotates by pushing the new password to each device. Case B (the common enterprise pattern): a user's separate elevated identity (e.g. PRV-jsmith / EDM-jsmith) lives in Cisco ISE (or in AD behind ISE), and network devices authenticate against ISE via TACACS+/RADIUS. Versus rotates the account ONCE at the authority; every device honours it automatically — no per-device sync.
Case A — a local account on the target. The device holds its own copy (a device-local admin, a database sa). Versus rotates it by pushing the new credential to that target and verifying — each target synced individually. The device authenticates locally; Versus is not in the login path.
Case B — a directory- / AAA-delegated account (the common enterprise pattern). Most organisations give each administrator a second, elevated identity — often prefixed PRV- or EDM- (e.g. PRV-jsmith) — used only for privileged device access, separate from their day-to-day account. That elevated account typically lives in Cisco ISE (in ISE's internal identity store, or in Active Directory that ISE federates to), and the network devices are configured to authenticate administrators against ISE via TACACS+ / RADIUS. Here the authority is ISE/AD, not the device. Versus vaults and rotates that account once, at the authority; every device that trusts ISE honours the change automatically the next time someone logs in — zero per-device syncs.
Versus connects to these authorities through the Authentication Providers integration (Integrations → Identity & Access): Cisco ISE, Cisco ACS, Active Directory / LDAP, Aruba ClearPass, Microsoft NPS / FreeRADIUS, Okta, Entra ID, Ping, Duo, RSA SecurID, and the major PAM stores. Marking a provider vault-managed tells Versus to vault, rotate, and audit the privileged and service accounts it holds — so a PRV- account is checked out, used, returned, and rotated at ISE/AD, and the devices simply keep asking ISE.
NoteIf what you actually want is "the external service checks with Versus at login time," that is a different capability — federating that service to Versus as an identity provider (OIDC / SAML / LDAP / RADIUS), or fronting access with the collector's inject-and-broker path so the human never handles a password. Vaulting a retrievable password and being the login authority are two distinct designs; Versus supports both, for different accounts.
9. Rotation
Rotation is verify-before-commit: Versus generates a new credential, applies it to the target device or system, and confirms the new credential actually works before writing the new vault version. If verification fails, the previous credential is restored — a botched rotation never locks you out of a device. A scheduler rotates on a policy interval (90 days by default) and skips secrets that are actively checked out.
FIG 9 · TWO-PHASE ROTATION
Rotation generates a new credential, pushes it to the target device or system, and verifies it works BEFORE committing the new vault version. If verification fails, the previous credential is restored — a failed rotation never locks you out. A scheduler sweeps by policy interval (default 90 days) and skips actively checked-out secrets.
10. Tamper-evident audit
Every vault operation — write, read, check-out, reveal, rotate — appends an audit event whose SHA-256 hash covers both its own contents and the previous event's hash. That makes the log a hash-linked chain: deleting or editing any historical entry breaks every hash after it, and a verifier walks the chain to report the exact point of tampering. This is in addition to the flat operational audit log used elsewhere in the platform.
FIG 10 · TAMPER-EVIDENT AUDIT
Every vault operation appends an event whose hash covers its own contents plus the previous event's hash — a blockchain-style linked list. Deleting or editing any past event breaks every hash after it, so verify_chain() pinpoints exactly where a log was altered.
11. Granting access — roles & entitlements
Access to a secret is the intersection of three layers, configured in different places:
A path policy maps a path glob (e.g. apps/ci/**) to a role and a set of capabilities. Roles come from Versus's RBAC; for federated logins, the identity provider's group → role mapping assigns them automatically at each login. Every user also gets a personal namespace — personal/<username>/** — scoped to them alone via ${user} templating, so individuals have private secret storage without an admin creating a policy per person.
12. Self-service access through App Builder
You can publish a no-code App Builder app that gives entitled users a simple "copy this secret" surface — no admin console required. The picker only lists secrets the requester can actually read (it is filtered by the same per-path policy), and the copy goes through the identical policy assertion and audit as any other read. Access is entitlement-gated end to end; the app never widens what a user is allowed to see.
FIG 11 · APP-BUILDER SELF-SERVICE
A no-code App Builder app can expose a 'Secret Copy' surface. The picker only lists secrets the requester is entitled to read (per-path policy filtered), and the copy/reveal goes through the same policy assertion + audit as any other read — the user never needs an admin console. Access is entitlement-gated end to end.
13. Transport & trust boundaries
Secrets in flight are protected end to end. The public API terminates TLS on the app server (a co-located reverse proxy; the app's plain listener is never exposed on the network). Collectors authenticate with a short-lived ES256 bearer token (TLS 1.2+) signed with their enrollment key — a certificate-derived identity, not a client certificate. The vault API requires an authenticated bearer — a human JWT for people, an app-principal token for machines.
What protects the vault. The vault encrypts what it stores, but it is itself unlocked by the master KEK, which is unlocked by VAULT_KEK/SECRET_KEY. Those bootstrap keys therefore sit at the root of trust and must be delivered by your secrets manager and escrowed. This is inherent to any software vault — a key has to come from somewhere the vault can read at boot.
14. Hardware-backed keys & external KMS
For the highest-assurance deployments, the master KEK and TLS private keys can be anchored in hardware. Versus integrates with external key stores at two layers, and we are explicit about which is native versus an integration:
External KMS / HashiCorp Vault for bootstrap keys — the field/KEK master keys and database URL are delivered from your KMS or secrets operator at deploy time (sealed-secrets / external-secrets), rather than living in a raw environment variable. This is a deployment integration, configured in your orchestration layer.
HSM-backed KEK / non-exportable TLS keys — the certificate-lifecycle module models HSM custody (Thales, Entrust, AWS/Azure/Google Cloud HSM, PKCS#11) so that private keys can be generated and used inside an HSM and never exported. Binding the vault's master KEK to an HSM is the top item on the vault key-management roadmap; today the KEK is a strong software key (VAULT_KEK or PBKDF2-derived).
Collector Mode C — for device credentials, the customer's own HashiCorp Vault is a first-class backend today (see §6).
NoteWe state the boundary plainly because your reviewers will test it: application secrets are envelope-encrypted with per-secret AES-256-GCM keys; TLS private keys use an independent AES-256-GCM envelope; the master KEK is a strong software key today with a documented path to HSM anchoring; and the bootstrap keys are sourced and escrowed from your KMS. No claim here depends on a capability that isn't in the product.