Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Secret handling discipline

Related:

Context

braid handles two kinds of LUKS secret material in process memory:

  • user-entered passphrases used for cryptsetup open, verify, format, and keyfile enrollment;
  • generated keyfile bytes used for slot-1 auto-unlock enrollment.

These values must exist briefly in process memory, but they should not escape that narrow window through ordinary Rust strings, buffered readers, command arguments, debug output, or long-lived scopes.

Decision

LUKS passphrase plaintext is represented by secret::Passphrase, a newtype around Zeroizing<String>. LUKS keyfile byte buffers remain Zeroizing<[u8; KEYFILE_SIZE]> because the generated bytes never leave the function frame that writes them.

Every passphrase read path must use unbuffered Read, not BufRead, and must consume input one byte at a time into pre-sized zeroizing storage. This avoids std-internal buffering that can retain plaintext outside braid-owned Zeroizing values. Confirmation reads in cli/src/confirm.rs intentionally accept Read, not BufRead, for the same reason: confirmation must not pre-drain bytes needed by a later --passphrase-stdin read.

Every secret-bearing read must enforce a hard byte cap while reading. Passphrase reads use PASSPHRASE_MAX_BYTES = 64 * 1024; confirmation reads use CONFIRM_MAX_BYTES = 256. New secret-read sites must declare and enforce their own cap instead of allowing unbounded growth of a zeroizing buffer.

Anything inside a Passphrase must reach subprocesses through CommandRunner::run_with_stdin, never through CmdRequest::to_argv. The Passphrase::expose_secret() method is the grep-friendly plaintext egress point for these handoffs. ps(1) must never be able to surface a passphrase.

Generated random secrets must drop before any later syscall whose duration is unbounded. In particular, generated keyfile bytes are scoped so the Zeroizing<[u8; KEYFILE_SIZE]> is dropped before the durability sync_all() on the written file.

Every type that owns secret bytes must implement Debug with redacted output. The canonical rendering is <redacted>.

braid does not use in-process passphrase equality as an authentication mechanism. Normal passphrase verification is delegated to cryptsetup/LUKS. The only current in-process comparison is the local double-prompt confirmation flow for fresh formatting, where braid checks that two user-entered strings match before one becomes the new pool passphrase.

Threat Model

These rules harden braid’s in-process memory image against accidental plaintext retention in process snapshots, core dumps, and swap residue. They do not defend against a privileged attacker on the running host with ptrace, /dev/mem, root access to /proc/<pid>/mem, or equivalent capabilities.

The target invariant is narrower: no plaintext beyond the smallest practical in-process window, and no untyped plaintext values at module boundaries.