Configuration
This is the complete reference for how kovra is configured: the environment variables it reads, and the files it uses.
Environment variables
Section titled “Environment variables”| Variable | Default | What it does |
|---|---|---|
KOVRA_VAULT_DIR |
~/.vaults |
Override the vault registry root. A vault in another folder keeps its master key in a Keychain item of its own (below). |
KOVRA_PASSPHRASE |
(unset) | Switch to passphrase mode: derive the master key with Argon2id instead of using the OS keyring. Refused on macOS. |
KOVRA_CONFIRMER |
development builds: biometric (falls back to file) |
Confirmation channel: biometric (Touch ID) or file (the kovra approve broker). Development builds only: a released kovra on a Mac always asks with Touch ID or your login password, and every build ignores it inside a Claude Code session. |
KOVRA_UI_NO_CONFIRM |
(unset) | Skip the Dashboard launch confirmation (same as kovra ui --no-confirm). |
KOVRA_MCP_ENVIRONMENTS |
* |
MCP session scope — addressable environments (comma list, or * for any). |
KOVRA_MCP_PROJECTS |
* |
MCP session scope — addressable projects (comma list, or * for any). |
The vault directory
Section titled “The vault directory”The registry root (default ~/.vaults, or KOVRA_VAULT_DIR) holds:
~/.vaults/ global/ # the global vault — sealed per-secret records + a sealed index projects/ <name>/ # one directory per project vault, same layout keycheck.sec # the key check: kovra refuses a master key that does not open it allowlist.sec # the executor allowlist, sealed under the master key kdf.salt # passphrase-mode only: the non-secret Argon2 saltEvery record is sealed at rest; coordinates aren’t exposed as plaintext filenames. See Cryptography for the at-rest format.
The master key in the Keychain
Section titled “The master key in the Keychain”Each vault folder keeps its master key in an item of its own in the Keychain (the
Credential Manager on Windows), under the service kovra:
~/.vaultsuses the itemmaster-key, as every version of kovra has.- A vault in another folder (
KOVRA_VAULT_DIR) usesmaster-key.<…>, 16 characters derived from the folder’s path. A vault you create to try something out never touches the key of your real vault: itskovra init --forceorkovra key import --forcereplaces only its own item. - After updating, a vault in another folder that used
master-keyuntil now adopts that key into its own item, once, and only if the key opens every one of its records. kovra says so once;master-keystays as it was. A vault that holds nothing yet does not adopt it: itskovra initcreates a key of its own. - Moving a vault’s folder: the vault no longer finds its item at the new path. Run
kovra key exportbefore you move it andkovra key importafter.
When you delete a vault’s folder, its master-key.<…> item stays in the Keychain. It
opens no vault that still exists.
.mcp.json
Section titled “.mcp.json”kovra setup registers the MCP server here so your agent can launch it. The
env block carries the MCP session scope:
{ "mcpServers": { "kovra": { "command": "kovra", "args": ["mcp"], "env": { "KOVRA_MCP_ENVIRONMENTS": "dev,test", "KOVRA_MCP_PROJECTS": "my-app" } } }}This is what bounds what an agent over MCP can address (* = any). It’s distinct
from agent.toml, which scopes the ssh-agent.
agent.toml — the ssh-agent scope
Section titled “agent.toml — the ssh-agent scope”The governed ssh-agent reads its scope from
<vault-root>/agent.toml. The format is intentionally tiny — two array keys, with
# comments:
# <vault-root>/agent.toml — kovra ssh-agent scopeenvironments = ["dev", "test"] # omit (or []) → any environmentprojects = ["api"] # omit (or []) → global + any projectTwo things are not configurable here, by design: the operation set is fixed to
metadata + inject (an ssh-agent never reveals a private key), and when the
file is absent the agent serves any environment/project — still never revealing,
and still requiring a bioProve on every
high/prod signature.
The .env.refs grammar
Section titled “The .env.refs grammar”.env.refs maps local environment-variable names to sources. It holds
addresses, never values, so it’s safe to commit. One mapping per line:
| Form | Meaning |
|---|---|
project = <name> |
Bind the file to a project vault (resolution targets it). |
NAME=secret:<env>/<comp>/<key> |
A vault coordinate. May use ${ENV}; an optional | fallback applies if it doesn’t resolve. |
NAME=secret://global/<env>/<comp>/<key> |
Force resolution against the global vault, bypassing the project. |
NAME=${env:VAR} |
A passthrough from the execution environment. Supports ${env:VAR | fallback}. |
NAME=literal |
A literal value (not a secret), e.g. PORT=8080. |
Rules that keep it safe:
- No values, ever — addresses only, so a leaked
.env.refsexposes nothing. ${ENV}is substituted bykovra run --env <e>inside a coordinate’s environment segment;${env:VAR}reads from the surrounding environment.- Cross-variable interpolation is rejected — you can’t compose one secret inside another variable’s string (that composed string would get logged).
- Resolution is a single ordered pass over the file.
See The .env.refs contract for the narrative version.