Security
What encino defends against, what it explicitly does not, and how much passphrase is enough. The out-of-scope list below is stated in full so the design is not mistaken for something stronger than it is.
This has not been independently audited.
The construction below is built from well-reviewed primitives and its format is documented well enough to audit or reimplement from. That is not the same as having been reviewed, and the difference matters.
Assets
What is protected, and what an observer with the disk learns anyway.
| Asset | Protected? | Detail |
|---|---|---|
| Document contents | yes | XChaCha20-Poly1305, per-document keys |
| Document filenames | yes | held only in the encrypted index |
| Folder and subfolder structure | yes | the blob store is flat |
| Document count | no | the number of blobs is visible |
| Document sizes | partly | padded to a bucket, so only the bucket shows |
| Access times | no | filesystem timestamps on blobs are visible |
Sizes are padded with Padmé, floored at 4 KiB: every document under 4 KiB is exactly the same size on disk, and above that the padding costs at most 12% and leaves roughly log log n bits of the size visible. That is enough to tell a note from a scan, and not enough to confirm a specific document by its size — which is the attack that mattered. The count and the access times remain visible and are recorded in the format spec as candidates for a future revision; hiding the count needs decoy blobs, which carry an ongoing storage cost rather than a one-off one.
In Scope
Lost or stolen machine
The vault is at rest. An attacker with the disk learns the number of documents and each one's size bucket, and nothing else. Recovering contents requires the passphrase or the recovery code.
Leaked backup or cloud-sync copy
Because the vault's resting state is encrypted, a backup captures ciphertext. This is the reason for per-file checkout: a design that decrypts the whole folder would leak plaintext revisions into a sync provider's version history, where the user cannot delete them.
Another account on the same machine
Blobs carry no filenames and the index is sealed. The session agent is reachable only by the account that started it, enforced twice on each platform so that neither mechanism is load-bearing alone. On Windows the pipe's security descriptor grants access to the owning SID and nothing else — protected, so no inherited entry can widen it — and remote clients are refused outright. On Linux the socket is 0600 inside a 0700 directory the process has verified it owns, and every accepted connection's SO_PEERCRED uid is checked against our own before the request is read.
Casual inspection
Someone who opens the folder while it is locked sees an empty directory and a .encino folder of opaque blobs.
Out of Scope
Code execution while the vault is unlocked
Malware running as the user can ask the agent to unwrap documents, or read the plaintext of a checked-out file directly. The session agent limits the window but cannot close it. This is the fundamental limit of at-rest encryption on a compromised host.
Keylogging
A kernel or user-mode keylogger captures the passphrase as it is typed. No amount of key stretching helps.
Memory capture
A full memory dump or a kernel debugger can expose the master key. VirtualLock prevents the key from reaching the pagefile but does not defend against an attacker who can read process memory.
Physical coercion
encino has no duress passphrase and no deniable volume. If you can be compelled to reveal the passphrase, the documents are revealed.
Guaranteed erasure on SSDs
close overwrites plaintext before unlinking, but on flash storage wear levelling and the FTL mean overwriting a logical block does not reliably erase the physical one. The real mitigation is volume-level encryption. The CLI states this once at init rather than implying a guarantee it cannot make.
A weak passphrase
Argon2id raises the cost of guessing but cannot rescue a passphrase that is short or reused. The passphrase and the recovery code are the entire security of the vault.
Hibernation while unlocked
The one case that happens by accident rather than by an attacker's effort
Hibernating writes all of physical memory to hiberfil.sys or the swap device, and locked pages are not exempt: VirtualLock and mlock keep the key out of the page file, which is a different thing. Closing a laptop lid is enough, since sleep escalates to hibernation once its timer expires — the default on most laptops.
- Run
encino lockbefore closing the lid. It is the only action that removes the key from memory. --timerbounds the exposure but does not remove it. A machine that hibernates while a session is live writes the key out before the timer can fire.- Encrypt the whole volume, which covers the hibernation file. BitLocker protects
hiberfil.sys; on Linux the swap device must be encrypted too, as encrypting only the root filesystem leaves swap in the clear.
Choosing a Passphrase
The passphrase is the only part of this design a human picks, which makes it the only part with any slack. The master key, the per-document keys, and the recovery code are all 256 bits of machine randomness. Nobody attacks the cipher; they attack the passphrase.
| Words | Entropy | 10³/s | 10⁴/s | 10⁶/s |
|---|---|---|---|---|
| 2 | 25.8 bits | 8 hours | 50 min | 30 sec |
| 3 | 38.8 bits | 7 years | 272 days | 3 days |
| 4 | 51.7 bits | 5.8 × 10⁴ yr | 5.8 × 10³ yr | 58 years |
| 5 | 64.6 bits | 4.5 × 10⁸ yr | 4.5 × 10⁷ yr | 4.5 × 10⁵ yr |
| 6 | 77.5 bits | 3.5 × 10¹² yr | 3.5 × 10¹¹ yr | 3.5 × 10⁹ yr |
| 8 | 103.4 bits | 2.1 × 10²⁰ yr | 2.1 × 10¹⁹ yr | 2.1 × 10¹⁷ yr |
Expected time is half the space, since the average search finds the answer midway. The middle column — ~10,000 guesses/sec against this vault's Argon2id parameters — is roughly twenty high-end GPUs. Memory-hardness is what keeps that rate so low; against a fast hash it would be billions per second and even five words would fall.
Reading it
- 2Crackable over a lunch break. encino produces it on request but warns, because 26 bits is not a secret.
- 3Falls within a year to a serious attacker. Not enough.
- 4Defensible against an individual, but only 58 years against a well-funded one — thinner than it looks.
- 5Beyond any practical attack.
- 6Exceeds the age of the universe by a factor of twenty-five even at the highest rate. This is the default, and it is the right one.
- 8Beyond six is decoration — it turns an impossible problem into a differently impossible one.
encino passphrase # six words, ~77 bits
encino passphrase --words 8 # ~103 bits
Take the suggestion as it came
Swapping a word you dislike, or reordering them into something that reads nicely, throws away the randomness that makes it strong. Words are drawn with the OS random source from EFF's Long Wordlist (7,776 words, 12.925 bits each), compiled into the binary. Rolling five dice against the printed list is just as good and trusts nothing at all.
Self-invented passphrases are the trap
Length is no defence, because people draw from a far smaller space than they think and attackers model that space well. A memorable six-word phrase may carry twenty-odd bits where a random one carries seventy-odd.
Substituting @ for a and 3 for e adds nothing if the rule is predictable — password crackers have applied leet rules by default for twenty years. Adding a word beats it on every axis.
The recovery code is as sensitive as the passphrase
The master key is wrapped independently under a passphrase-derived key and a 24-word BIP39 recovery code. Either opens the vault outright. Written on paper in a drawer, it is a bypass of every other protection here — it belongs in a safe or a safe deposit box, not beside the machine.
Losing both means the documents are gone
Permanently. No backdoor, no reset, no support path. This is what it means for the vault to have no server and no account — there is nobody who could help even if they wanted to.
Trust Assumptions
encino assumes the operating system, the Rust toolchain, and the audited crypto crates it depends on are not themselves compromised, and that the OS CSPRNG is sound. It makes no network connections and has no remote component, so there is no server to trust and no traffic to intercept.
Supply chain
Cargo.lock is committed, so builds are pinned to exact versions and hashes and a malicious new release of a dependency cannot reach you. The gap that leaves is the reverse — an advisory filed against a version already pinned, which nothing surfaces by itself. CI runs cargo audit --deny warnings weekly as well as on every push, and cargo update is treated as the step that deserves attention, since it is the only thing that moves the project off known-good pins.
Reporting a Vulnerability
Privately rather than as a public issue, so a fix can exist before the details do — via GitHub's Report a vulnerability button, which opens a private advisory. Worth including: what an attacker gains, the conditions needed, and a way to reproduce it. A concrete failing case is worth more than a description.
In scope
Anything that breaks a promise the threat model makes.
- Recovering document contents or filenames from a locked vault
- Defeating authentication on the index, a blob, or a key wrap
- Silent data loss or corruption, including anything that survives the journal
- A path that leaves plaintext on disk after close or lock reports success
- One user reaching another's session agent
- Escaping the vault folder through a crafted document name
Out of scope
The documented limits — design decisions rather than defects.
- Attacks requiring code execution as the user while the vault is unlocked — the boundary defended is the disk, not other processes of the same user
- Recovering plaintext from an SSD after shredding; volume encryption is the mitigation, and the tool says so at init
- Weak passphrases — Argon2id raises the cost of guessing, it cannot rescue a poor choice
- The document count, and each one's padded size bucket, being visible on disk
If you are unsure which side something falls on, report it. A judgement call in your favour costs a few minutes; the opposite could cost someone their documents. This is a personal project with no team behind it, so reports are acknowledged as soon as reasonably possible rather than same-day.