UnENVerse Find a guide: importing, command line, nodes

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_file chunk 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": {} } }
CodeExitMeans
error1Unclassified
2Usage error
not_found3No such entry, project, chunk, user or class
ambiguous4Matched several; details.candidates lists them
denied5Authentication failed, or the permission is missing
conflict6Already exists, or a concurrent write won
unavailable7Vault or server unreachable, or locked
needs_confirmation8Destructive command, no --yes, no terminal
redacted9The output would contain secrets
invalid10Well-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

GroupWhat it covers
list get export diffReading and comparing
entryAdd, set, remove, tag, pin, compromise, rotate, history
project category tagsStructure
env render exec emit curl cookieMaterialising values without printing them
genSecrets, passwords, certificates, SSH keys, entropy sources
import import-vault watch migrateGetting existing secrets and configs in
backup recover opsBackups, streamed recovery packages, durable operations
scan status doctor auditChecking
poolKey pools
user class perm login whoamiUsers, tokens, classes, permissions, sessions
totp codes oauthAuthenticator codes, recovery codes, OAuth refresh
node history blast-radiusNodes, the config time machine, compromise scoping
check shieldCross-chunk config checks, output-stream filtering
describe completionsThe 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.