Guides / The command line
The command line
unv exists so that a script, a CI job or an AI agent can drive the whole vault without a secret value ever entering its output.
The orchestrator decides what happens. Values move from the vault to the target without passing through the orchestrator's context.
1. Redaction is the default
Every stdout path masks stored values as sha256: plus twelve hex characters. In JSON:
{ "redacted": true, "fingerprint": "sha256:d4291adb444f", "length": 40 }
--reveal opts back in. Fingerprints are stable per value, so equal fingerprints mean equal secrets: a caller can detect drift and duplication without reading anything. An empty value fingerprints as empty, never as a hash, so "unset" and "set to something" cannot look alike.
- A whole
env_filechunk is masked and its per-field flags are ignored, because the first unflagged password would otherwise be the one that leaks. - A
${ref}is never masked. It is a pointer, and leaving it readable is what lets an agent wire configs together blind.
2. Three ways to get the real value out
None of them touch stdout.
unv get GitHub --out token.txt # straight to a file
unv exec -- ./deploy.sh # into the child's environment
unv render nginx.conf.tpl --out /etc/nginx/nginx.conf
This is enforced by construction: an exporter cannot obtain a real value unless it is handed the materialising resolver. A vault-wide export to stdout is refused outright with exit code 9, because a masked .env looks deployable and is not.
3. Secrets that are never seen at all
unv entry add Stripe --generate
unv entry rotate Stripe --generate
unv user token new deploy --out token.txt
The value is created inside the process that stores it, and the caller gets a fingerprint back. user token new checks the output policy before minting, so it never leaves a live token nobody can read.
4. A machine-readable envelope
{ "ok": true, "command": "entry.add", "data": { ... } }
{ "ok": false, "error": { "code": "not_found", "message": "...", "details": {} } }
| Code | Exit | Means |
|---|---|---|
error | 1 | Unclassified |
| 2 | Usage error | |
not_found | 3 | No such entry, project, chunk, user or class |
ambiguous | 4 | Matched several; details.candidates lists them |
denied | 5 | Authentication failed, or the permission is missing |
conflict | 6 | Already exists, or a concurrent write won |
unavailable | 7 | Vault or server unreachable, or locked |
needs_confirmation | 8 | Destructive command, no --yes, no terminal |
redacted | 9 | The output would contain secrets |
invalid | 10 | Well-formed request, invalid input |
The difference between 5 and 7 matters: a caller retries a 7. A certificate that fails its pin must not look like "cannot reach server", or an agent would loop against a machine in the middle. --dry-run is enforced at the single write point, so a command that forgets to check the flag still cannot write.
5. unv describe
The whole command tree, every flag, the exit codes, the envelope and the redaction rules, as one JSON document generated from the same definition the binary runs on. It cannot describe a flag that does not exist, which is why an agent should read it instead of guessing from error messages.
unv describe | jq '.commands[] | select(.name == "entry")'
Certificate pinning
The CLI and the desktop app share one certificate verifier, so a self-signed unv-server is trusted or refused the same way by both.
unv --fingerprint <sha256> --server https://vault.lan:8743 list
unv --ca-cert /etc/ssl/private-ca.pem --server https://vault.lan:8743 list
unv login --server https://vault.lan:8743 --tofu
--tofu is trust on first use: it performs the handshake, prints the fingerprint, sends no credentials and stores the pin beside the session token. It refuses to run again against a server that already has a pin, because a certificate that changed underneath you is exactly what pinning exists to notice. There is no --insecure flag.
Getting the password in without putting it in argv
unv --password-command 'pass show unv' list
unv --password-file /run/secrets/vault-pw list
unv --env-file /srv/unv/.env enrich --online --apply
UNV_PASSWORD=... unv list
Signing in as a named user
unv login --server https://vault.lan:8743 --user alice
unv whoami
unv sessions
unv logout --all
Sessions are cached in sessions.json with owner-only permissions, filed by subject. A rejected session is cleared and the error tells you to log in again. On Windows the file inherits the directory ACL; that gap is documented rather than hidden.
Idempotent provisioning
unv entry add Stripe --if-missing
unv entry set Stripe --create
unv project add Web --if-missing
Re-running a provisioning script is not an error, and it never overwrites a secret. Lookups refuse ambiguity: unv entry rm git with both GitHub and GitLab present lists both and exits 4. A confirmation prompt on a non-terminal refuses rather than assuming yes.
unv doctor
Everything that can be checked about a vault without changing it: database integrity, the salt file, file permissions, schema, pool state and the audit hash chain. It exits 0 when all pass and 10 when something is actually broken, and it runs even when the vault will not open, which is the case it exists for. There is no --repair for a missing salt, because nothing can reconstruct sixteen bytes of random output.
Command groups
| Group | What it covers |
|---|---|
list get export diff | Reading and comparing |
entry | Add, set, remove, tag, pin, compromise, rotate, history |
project category tags | Structure |
env render exec emit curl cookie | Materialising values without printing them |
gen | Secrets, passwords, certificates, SSH keys, entropy sources |
import import-vault watch migrate | Getting existing secrets and configs in |
backup recover ops | Backups, streamed recovery packages, durable operations |
scan status doctor audit | Checking |
pool | Key pools |
user class perm login whoami | Users, tokens, classes, permissions, sessions |
totp codes oauth | Authenticator codes, recovery codes, OAuth refresh |
node history blast-radius | Nodes, the config time machine, compromise scoping |
check shield | Cross-chunk config checks, output-stream filtering |
describe completions | The contract, and shell completion |
Anything the desktop app can do, the CLI can do, and the reverse; the exceptions are written down in one file and tested.