Security invariants
These are quoted from the project’s own engineering documentation, where they carry the heading “these are non-negotiable” and where every one has a corresponding test. They are reproduced rather than paraphrased, because a paraphrase is where the caveats go missing.
1. The server is zero-knowledge. It NEVER sees plaintext secrets or unwrapped keys. Every value the server stores is ciphertext or a public key. If a task seems to require the server reading a secret, stop and flag it — do not add server-side decryption.
2. All encryption and decryption happen on the client only. Never send a master password, a derived key, or a plaintext secret to the server. Not for “convenience,” not for logging, not for search.
3. Authentication is SEPARATE from key derivation. Logging in proves identity; it must NEVER be the thing that decrypts the vault. The auth token gives API access, not secret access.
4. NEVER log, print, or include in error reports: secret values, master passwords, derived keys, wrapped keys, or recovery keys. Scrub these from all logs and telemetry.
5. Least privilege by default. A new project member gets
no accesson every environment until explicitly granted. Access is granted, never assumed.
6. Revocation implies rotation. Removing a grant stops future access; it cannot un-share already-pulled secrets. Any “revoke from a sensitive environment” flow must offer/perform key rotation.
7. Treat the clipboard and disk as hostile. Auto-clear copied secrets (~20s). Don’t write plaintext secrets to disk; downloading a plaintext
.envmust be a deliberate, friction’d user action.
8. Zero the master/vault key from memory on lock. Use
zeroize(Rust) for key material; lock on idle timeout and on OS sleep/screen-lock.
What enforces each one
Section titled “What enforces each one”Rules are worth what enforces them. These are not aspirations kept in a document:
| Invariant | Enforced by |
|---|---|
| 1, 2 | The server depends on the wire-format crate only, never the crypto crate — and a CI check fails the build if that dependency edge ever appears. There is no code path from a stored blob to plaintext |
| 3 | Authentication and key derivation are separate code paths; the API token never reaches a decrypt function |
| 4 | Workspace lints deny println!, eprintln! and dbg! across the codebase, with no-console as the web-layer equivalent. Secret types carry redacting debug implementations, and a secret scanner runs on every commit |
| 5 | The schema has no default for an environment role, and the absence of a grant row is the no-access state — “accidentally granted” is not representable |
| 6 | Revoking surfaces the rotation prompt; rotation is one atomic server-side transaction |
| 7 | The clipboard guard is write-only with a generation-guarded ~20s clear; export warns on every invocation and refuses to write inside a git working tree |
| 8 | Locking is a consuming drop that zeroizes, so there is no state that can represent “locked but holding secrets” |
That last row is the pattern the whole codebase uses, and it is the most useful thing on this page: the safe behaviour is made structural rather than remembered. “Locked but still holding decrypted secrets” is not a bug avoided by being careful — it is a state the types cannot express.
Two rules about process
Section titled “Two rules about process”Stop-and-flag protocol: if any task appears to require violating an invariant, stop before writing code — name the invariant, explain the conflict, propose alternatives. Never implement a workaround “temporarily.”
NEVER weaken, skip, or delete a security test to make the build pass. Fix the code, or stop and flag it. A failing invariant test means the code is wrong, not the test.
We publish these because publishing them makes them harder to quietly abandon. Today they are checkable in principle. When the client source is public they become checkable in practice, which is the point of publishing it.