Skip to content

Configuration

This is the complete reference for how kovra is configured: the environment variables it reads, and the files it uses.

VariableDefaultWhat it does
KOVRA_VAULT_DIR~/.vaultsOverride the vault registry root.
KOVRA_PASSPHRASE(unset)Switch to passphrase mode: derive the master key with Argon2id instead of using the OS keyring.
KOVRA_CONFIRMERbiometric (falls back to file)Confirmation channel: biometric (Touch ID / Windows Hello) or file (the kovra approve broker).
KOVRA_UI_NO_CONFIRM(unset)Skip the Web UI launch confirmation (same as kovra ui --no-confirm).
KOVRA_RECIPIENT_KEY(unset)Private ed25519 key used by kovra unpack (instead of --identity-file); kept out of argv.
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 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
kdf.salt # passphrase-mode only: the non-secret Argon2 salt

Every record is sealed at rest; coordinates aren’t exposed as plaintext filenames. See Cryptography for the at-rest format.

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-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.

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 scope
environments = ["dev", "test"] # omit (or []) → any environment
projects = ["api"] # omit (or []) → global + any project

Two 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.

.env.refs maps local environment-variable names to sources. It holds addresses, never values, so it’s safe to commit. One mapping per line:

FormMeaning
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=literalA literal value (not a secret), e.g. PORT=8080.

Rules that keep it safe:

  • No values, ever — addresses only, so a leaked .env.refs exposes nothing.
  • ${ENV} is substituted by kovra 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.