Skip to content

Keep secrets in the vault

The vault stores secrets encrypted in the store, instead of in files:

echo "$KEY" | wardrik vault put ANTHROPIC_API_KEY --for api_key # the deployment's
echo "$KEY" | wardrik vault put ANTHROPIC_API_KEY --org acme --for api_key # acme's
echo "$CRM" | wardrik vault put CRM_KEY --user jon --for header # acme/@jon's, in the deployment's org
wardrik vault ls --org acme # names and purposes, never values
wardrik vault rm CRM_KEY --user jon
  • vault put reads the value from stdin. The daemon must be running.
  • With no flag, the secret belongs to the deployment. --org ORG sets an org. --user NAME sets a user in the deployment's org.

Each secret is stored --for one purpose, and only used for that:

--for Used as
api_key a key a model or tool calls a service with
client_secret an OAuth app's client secret, for wardrik provider add --secret
header an MCP server's header, for wardrik server add --secret
signing_secret a webhook's signing secret, for wardrik notify add --secret and triggers

An agent's model and tools look up a key by name, such as ANTHROPIC_API_KEY, in order:

  1. The agent owner's secrets: a user's or an org's.
  2. The user's org.
  3. The deployment's, if deployment_keys is true (the default).
  4. The daemon's environment.

To use its own key, an org stores a secret with the key's name. Set deployment_keys: false in config.yaml to require it of every org.

config.yaml reads them as ${NAME}. Agents can't read them, except API keys while deployment_keys is true.

The daemon reads its key from, in order:

  1. vault.keys in config.yaml.
  2. WARDRIK_VAULT_KEY in the environment, or in ~/.config/wardrik/env.
  3. ~/.local/share/wardrik/vault_key, generated on first start.

To rotate it, list the new key first. The first key encrypts, and any key decrypts:

vault:
keys: ["${WARDRIK_VAULT_KEY_NEW}", "${WARDRIK_VAULT_KEY}"]
  • Restart the daemon after changing a key.
  • A key is 32 random bytes, base64-encoded: openssl rand -base64 32.
  • Back up the key separately from the store. Together they decrypt every secret.
  • Without a key, nothing is stored, and calls that need a connected account fail.
  • Names aren't encrypted. Anyone who can read the store sees which owner has which secret names.
  • Over HTTP, secrets are at /v1/owners/{owner}/secrets. {owner} is one URL-encoded segment, such as acme%2F%40jon.