Threat model

This is the threat model for the CLI, not a third-party audit.

Assets

  • Plaintext secrets on disk after unseal or get

  • Per-file data keys in .vault/keys.*

  • The signed manifest (the trust root: paths plus blob digests)

  • Ciphertext blobs in git history (they outlive rotate --rekey)

Adversaries

  • Anyone who can clone the remote (ciphertext and the encrypted name map)

  • A machine that is later lost (its private key still opens the wrap group it was given)

  • A local uid that can read world-readable temp files

  • A malicious pull request running in CI

What the commands buy

Command

Gives

Does not give

check

blob digests still match the signed manifest; no key needed

that you can decrypt; that git history is gone

rotate

a removed recipient can no longer unwrap the data keys

post-compromise secrecy for a leaked data key

rotate --rekey

new data keys and new payloads

erasure of old objects on the remote

status

local plaintext vs the recorded content hash

blob integrity

scan

unvaulted secret-shaped literals on disk

a complete detector

Trust root

The manifest is signed. Age payloads have no in-band signature; GPG v5 blobs do. v6 payloads are symmetric and unsigned in-band. Authenticity for those is the signed manifest plus the blob digest unseal and get check before decrypting.

Key files are encrypted and not signed. A tampered key file fails to decrypt rather than forging plaintext.

Limits that stay

  • Filenames are hidden; counts, sizes and mtimes of blobs are not.

  • There is no forward secrecy: old ciphertext in git history still opens with an old data key.

  • A private vault repository is the way to keep ciphertext off a public remote. Rotation cannot reach the GitHub fork network.

  • --allow-unsigned is a migration hatch. It is not a daily mode.

In-repo review (2026-09)

Not a third-party audit. Findings from reading src/ and the test suite, then fixing what was independently true:

  • add / rm / mv / add-dir used to drop the seal-key header (fixed).

  • add used to write v5 blobs into a v6 vault (fixed).

  • Plaintext and data keys used to land in .vault/.plain and pid-named /tmp files (fixed).

  • add-dir used to follow symlinks out of the named tree (fixed).

  • get on a GPG v5 blob used to strip the secret (fixed).

  • check used to pass a v6 vault with no key file (fixed).

  • One combined manifest.gpg is last-writer-wins on two adds; per-entry records under .vault/e/ close that (nimvault-k0fo).

Accepted:

  • Absolute paths outside $HOME with no root are the caller’s choice.

  • Unsigned GPG in-band signatures on key files stay unsigned so a second machine does not need the signer’s public key to decrypt.

  • Third-party review is not commissioned from this sitting.