Secrets vault
AiHummer stores every credential — channel tokens, SMTP/IMAP passwords, OAuth tokens, per-tenant LLM keys (BYOK) — in an encrypted secrets vault. The vault uses envelope encryption so that the value at rest is never readable from the database alone, and it is engineered so that a secret is never placed into the model context or the logs.
Envelope encryption
The vault uses a two-level key hierarchy:
- A master key (KEK) — supplied as
AIHUMMER_MASTER_KEY, a base64-encoded 32-byte value — wraps and unwraps the data keys. It never leaves the host and is never written to the database. - A per-tenant data encryption key (DEK) encrypts the actual secret values with AES-256-GCM (authenticated encryption). Each tenant has its own DEK, so one tenant’s keys cannot decrypt another tenant’s secrets.
Secret values are stored as ciphertext; the DEK is stored wrapped by the KEK. Decryption happens in memory at the moment a secret is needed (for example, when a connector authenticates), and the plaintext is discarded afterwards.
AIHUMMER_MASTER_KEY (KEK) ──wraps──▶ per-tenant DEK ──AES-256-GCM──▶ secret value
[!NOTE] The vault relies on PostgreSQL’s
pgcryptoextension. Make sure it is available in your database — it is part of the standard system requirements.
The master key
The master key is a bootstrap value: it is read from the environment at startup and is not configurable from the admin UI.
# /home/.aihummer/etc/gateway.env
# 32 random bytes, base64-encoded
AIHUMMER_MASTER_KEY=Base64Of32RandomBytes==
You can generate one with:
openssl rand -base64 32
[!WARNING] The master key is required to decrypt everything in the vault. Treat it like the root of your secrets and back it up separately from the database — if you lose it, the encrypted values cannot be recovered. See Operations for backup guidance.
Without the master key
If AIHUMMER_MASTER_KEY is not set, the vault and everything that depends on it
are disabled: secrets-at-rest storage, the credential vault and per-tenant
BYOK keys are all turned off. This is a deliberate fail-closed behaviour — the
product does not silently fall back to storing secrets in plaintext.
Secrets never reach the model
This is the most important property of the vault, and it is structural rather than a policy reminder.
[!DANGER] Secrets are never injected into the system prompt, the conversation history, or any model-visible text, and they are never written to logs. Tools that need a credential resolve it from the vault at call time, inside the gateway, and use it to authenticate the outbound request — the model only ever sees the result of the tool call, not the secret.
Because interactivity is driven by tool-calling (see Guardrails & prompt-injection defense), there is no path by which a prompt can ask the model to “read out” a stored secret: the model has no copy of it to read.
Shared and per-user credentials
The vault distinguishes between shared (workspace-level) credentials and personal (per-user) credentials. Per-user OAuth2 tokens obtained through the Connections flow are stored in the vault and resolved by the acting user, with a workspace fallback where appropriate. This lets the same tool act on behalf of different users with their own authorization, without ever exposing one user’s token to another.
Where to next
- RBAC & scoped API keys — who may read or change vault-backed configuration.
- Row-Level Security — per-tenant isolation in the database that backs the vault.
- Guardrails & prompt-injection defense — why the model can never be tricked into emitting a secret.