Vault and secrets¶
Every config file refers to a credential the same way: ${NAME}. Where that
NAME resolves is the vault chain's job. The chain has two tiers:
- Simple tier — no setup at all: environment, then an owner-only file.
- Secure tier — a backend whose server enforces who may read what.
The configuration shape is identical in both; only config/vault.yaml changes.
flowchart LR
P["${ARXIV_API_TOKEN}"] --> ENV["env"]
ENV --> FILE["secrets.local.sh<br/>0600"]
FILE --> SOPS["sops<br/>encrypted file"]
SOPS --> CMD["command<br/>pass / op / bws"]
CMD --> BAO["openbao<br/>server-enforced"]
First backend that holds the key wins.
The chain¶
config/vault.yaml — copied to $MINI_ORK_HOME/config/vault.yaml — lists the
backends in precedence order:
backends:
- type: env
- type: file
# - type: sops
# file: config/secrets.enc.yaml
# - type: command
# argv: ["pass", "show", "mini-ork/{key}"]
# - type: openbao
# address: http://127.0.0.1:8200
# mount: secret
# token_env: OPENBAO_TOKEN
# paths:
# - mini-ork/collector
# - mini-ork/reviewer
If the file is absent, the chain is simply env then file. That is the
whole local story — no daemon, no server, no account.
Backends¶
| Type | Holds | Best for | Notes |
|---|---|---|---|
env |
Process environment | Debugging, CI injection | Keep it first so one export rotates a value for one invocation |
file |
secrets.local.sh |
A single laptop | Default path $MINI_ORK_HOME/config/secrets.local.sh; override with MINI_ORK_SECRETS |
sops |
An encrypted YAML document | Git-committable team secrets | age or PGP recipient; still no server |
command |
Any executable printing one value for a key | pass, op, bws, infisical |
{key} is substituted into one argv element, never through a shell |
openbao |
Self-hosted OpenBao (the MPL-2.0 OpenBao fork of Vault) | Shared, audited, cloud-scale runs | The server enforces which paths a token may read |
file backend details¶
The file is parsed, never sourced and never executed. The parser accepts
simple export NAME=value records only. Keep it owner-only (0600); the file is
gitignored by the **/secrets.local.* rule.
Never commit real keys. The template is config/secrets.example.sh.
command backend details¶
The command receives one value on stdout. {key} is substituted into an argv
element, so a key name containing shell metacharacters cannot become a command.
openbao details¶
OpenBao is the recommended secure tier because least privilege is enforced by the server, not by mini-ork:
- Prefer a short-lived, path-scoped token over a root token.
- Issue one KV-v2 path per node role; a collector's token cannot read the reviewer's path.
- The token itself can live in
OPENBAO_TOKEN, or in the vault — but then something still has to hold that.
Fail-closed behaviour¶
A missing credential is an error, not an empty string. If a config references
${NAME} and no backend has it, the operation fails with a clear message naming
the variable. Nothing silently dispatches with an empty key.
The one deliberate exception is a lane whose kind does not need a key:
anthropic-native with an ambient Claude Code login, codex-native,
opencode-native. Those are allowed to be unkeyed by design.
Inspecting without leaking¶
mini-ork vault status # which backends are wired, and are they ready
mini-ork vault which NAME # which backend holds that name
Both commands print names and locations only. No value ever leaves through a command's stdout.
To set a key interactively:
configure discovers the keys a lane or workflow needs, prompts securely, and
writes secrets.local.sh atomically with 0600 permissions. For automation,
pipe NAME=value records to configure --from-stdin <lane> — never pass a key
as a command-line argument, where it lands in shell history and process lists.
Using a variable¶
Anywhere a config value is read, the ${NAME} form is resolved at the boundary:
env:
ARXIV_API_TOKEN: ${ARXIV_API_TOKEN}
OPTIONAL_FLAG: null # declare the variable absent for this node
A node's env: declarations are resolved from the secret store at the node
boundary. A run-scoped config/env.yaml overrides them.
Rotation¶
- Update the value in the highest-precedence backend you control.
- Run
mini-ork vault which NAMEto confirm which backend now answers. - Restart any long-lived process that cached the old value.
An environment export wins for that invocation only — which is exactly what you want while rotating: prove the new value works, then persist it.
Leaked credential protocol
If a key reaches a terminal transcript, a log, or a commit, treat it as exposed. Rotate it at the provider first; deleting the text afterwards does not undo the exposure.
Related¶
- Providers and lanes —
api_key_envnames. - Environment variables — resolution rules.
- Security — threat model and operator hardening.