============ Threat model ============ .. contents:: 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 --------------------- .. table:: +--------------------+-------------------------------------------------------------+------------------------------------------------+ | 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.