black-bag
black-bag is a zero‑trace, ultra‑secure, CLI‑only password manager for high‑risk operators. It ships as a single Rust binary with strict defaults (Argon2id time=10; lanes≥4), ML‑KEM‑1024 cascaded wrapping, XChaCha20‑Poly1305 encryption, and meticulous I/O hygiene (no stdout secrets by default, no temp files, atomic 0600 writes). Cross‑platform (macOS/Linux/Windows).
Core ideas:
- Secrets never go to stdout by default; reveals are TTY‑only unless you consciously opt into unsafe modes.
- Strong crypto: Argon2id → ML‑KEM‑1024 → 32‑byte DEK → XChaCha20‑Poly1305 for payload and per‑record secret fields.
- Strict limits and locking: page‑locking (best effort), input caps, constant‑time ops, sanitized errors.
Contents
- Install and Build
- Quick Start
- Operations Guide (day‑to‑day flows)
- CLI Reference (every command + flags, with examples)
- Security Model (short)
- Technology Deep Dive (all primitives, flows, AADs, sidecars)
- Vault Format (types, blobs, limits)
- Configuration (env/feature flags)
- Troubleshooting
Install and Build
# From crates.io (recommended)
# Local build
Tips:
- Use an encrypted disk; avoid shell history (e.g.,
HISTCONTROL=ignorespace). - For mission shells, set
BLACK_BAG_HARD_MODE=1to force TTY‑only and disable unsafe stdout/clipboard overrides.
Quick Start
# 1) Initialize vault (256 MiB Argon2 memory)
# 2) Add a login
# 3) List masked summaries
# 4) Reveal one record on a TTY
# 5) Add a TOTP seed (prefer SHA‑256)
# 6) Rotate wrapping keys after missions
# 7) Split recovery material (3‑of‑7)
Operations Guide
Create and Unlock
black-bag init --mem-kib 262144 --argon-lanes autoprompts for a strong passphrase (≥14 NFKC chars and zxcvbn≥3).- On first use of commands like
list,get,add, the CLI prompts to unlock.
Add Records (selected families)
- Login:
add login --title "Portal" --username alice --url https://example --tags prod - Note:
add note --title "Protocol" --tags red-teamthen paste body (Ctrl‑D to end). - API:
add api --service intel --environment prod --access-key AKIA... --scopes read,write - TOTP:
add totp --issuer GitHub --account you@example --secret-file ./secret.txt --algorithm sha256- Safer alternative:
printf 'otpauth://...' | black-bag add totp --otpauth-stdin - Optional ASCII QR: add
--qr --confirm-qr.
- Safer alternative:
- SSH/PGP/Recovery:
add ssh|pgp|recoverythen paste private/armored payload; stored encrypted.
Search and Inspect
list(masked): filter with--kind,--tag,--query, or--fuzzy.get <UUID> --revealshows secrets on TTY. Clipboard copy requires build feature and--unsafe-clipboard.
Rotation, Passphrase Changes, Migration
rotate [--mem-kib N]refreshes KEM and rewraps DEK (updates header MAC/epoch).passwd [--mem-kib N] [--argon-lanes auto|N] [--rekey-dek]changes passphrase and optionally rekeys payload DEK.migratebumps an older vault to latest on‑disk version and recomputes header MAC.
Recovery and Backups
- Shamir split:
recovery split --threshold 3 --shares 7outputs shares and a share‑set ID (commit). Store separately. - Combine:
recovery combine --threshold 3 --shares 1-<b64>,2-<b64>,3-<b64> [--set-id ...] [--raw]--rawemits binary to TTY; otherwise base64.
- Backup check:
backup verify --path ~/.config/black_bag/vault.cborvalidates public integrity sidecar without passphrase.
CLI Reference (Complete)
Global flags (prefix any subcommand):
--unsafe-stdout– allow secrets to flow to stdout/JSON (default off; prefer TTY)--require-mlock– require page‑locking or abort--emit <tty|stdout|json>– preferred output mode for non‑reveal flows (stdout/json require--unsafe-stdout)--format <text|json|ndjson>– output format for non‑secret results (defaults totext)--schema-version <N>– include JSON schema version in machine‑readable output (defaults to1)--quiet– suppress warnings and non‑essential notices--agent <none|keychain>– enable keychain agent (feature‑gated)--unsafe-clipboard– allow copying secrets to clipboard (feature‑gated)--duress– operate on a separate duress vault file- Env hard mode:
BLACK_BAG_HARD_MODE=1forces TTY‑only/clipboard‑off regardless of flags
init
Initialize a new vault.
black-bag init --mem-kib <KiB> [--argon-lanes auto|N]
Args:
--mem-kib(default 262144; min 32768)--argon-lanesauto(CPU cores capped 8, min 4) or integer ≥4
add
Add records in families. Sensitive values are captured by prompt/stdin/file; never argv.
Common options: --title <str> --tags <t1,t2> --notes <str>
Families:
login–--username <str>--url <str>contact–--full-name <str>--emails <e1,e2>--phones <p1,p2>id–--id-type <str>--name-on-doc <str>--number <str>--issuing-country <str>--expiry <YYYY-MM-DD>(secret via prompt)note– paste body after prompt (Ctrl‑D to finish)bank–--institution <str>--account-name <str>--routing-number <str>(account number via prompt)wifi–--ssid <str>--security <str>--location <str>(passphrase via prompt)api–--service <str>--environment <str>--access-key <str>--scopes <s1,s2>(secret key via prompt)wallet–--asset <str>--address <str>--network <str>(secret key via prompt)totp–--issuer <str>--account <str>--secret-file <PATH>--secret-stdin--otpauth-stdin--qr --confirm-qr--digits 6..8--step <secs>--skew <steps>--algorithm <sha1|sha256|sha512>ssh–--label <str>--comment <str>then paste private keypgp–--label <str>--fingerprint <str>then paste armored private keyrecovery–--description <str>then paste recovery payload
Examples:
black-bag add login --title "Ops" --username alice --url https://ops --tags prod
black-bag add note --title "Protocol" --tags red-team
black-bag add totp --issuer GitHub --account you@example --secret-file ./secret.txt --algorithm sha256
list
black-bag list [--kind <family>] [--tag <tag>] [--query <text>] [--fuzzy]
Families: login, contact, id, note, bank, wifi, api, wallet, totp, ssh, pgp, recovery
get
black-bag get <UUID> [--reveal] [--clipboard]
Notes: --reveal requires TTY; --clipboard requires feature + --unsafe-clipboard and auto‑clears.
rotate
black-bag rotate [--mem-kib <KiB>]
doctor
black-bag doctor [--json]
Prints ready status, Argon2 params, record count, timestamps. JSON/NDJSON include schema and headerMacVerified.
passwd
black-bag passwd [--mem-kib <KiB>] [--argon-lanes auto|N] [--rekey-dek]
migrate
black-bag migrate [--pq ml-kem-1024|kyber1024|next] [--aead xchacha20poly1305|aes256gcm]
Notes: flags are accepted for forward compatibility; current builds keep ML‑KEM‑1024 + XChaCha20‑Poly1305.
export csv
black-bag export csv [--kind <family>] --fields <f1,f2,...> [--include-secrets] --unsafe-stdout
Common fields: id,kind,title,tags,summary,username,url,password,secret_key,totp_secret
backup verify
black-bag backup verify --path <vault.cbor>
recovery split / combine
black-bag recovery split --threshold <n> --shares <m> [--duress]
black-bag recovery combine --threshold <n> --shares <id-b64,...> [--set-id <base32>] [--raw] [--duress]
totp code
black-bag totp code --id <UUID> [--time <unix>]
selftest
black-bag selftest
version
black-bag version
Prints version + enabled features (PQC, AEAD, Argon caps, compiled features).
completions
Generate shell completions (print to stdout):
black-bag completions <bash|zsh|fish|pwsh>
help-man
Render a manpage to stdout:
black-bag help-man | man -l -
Stdout/Stderr & Schema Contract
- Secrets and warnings are sent to the TTY or stderr; machine‑readable data are sent to stdout.
- When
--format json|ndjson(or--json), aschemafield is included (default1). Field names remain stable across schema versions.
Exit Codes (Scriptable)
0OK2Input/usage/validation error3Integrity failure (e.g., header MAC, tag mismatch)4Unlock error (failed to unlock vault)5I/O or environment errors6Concurrency/locking errors7Policy violations (requires TTY, unsafe flags not set, etc.)10Internal/unknown error
Security Model (Short)
- KDF: Argon2id (time=10; lanes≥4, capped 8). Passphrase policy: NFKC, ≥14 chars, uniqueness, zxcvbn≥3.
- Wrapping: ML‑KEM‑1024 public key + ciphertext. Decapsulation secret sealed with KEK.
- Payload & secret fields: XChaCha20‑Poly1305 AEAD with distinct AADs; per‑record rDEKs for secrets at rest.
- Header integrity: keyed BLAKE3 MAC over critical header fields; verified on unlock (constant‑time compare).
- Public integrity sidecar: Blake3 tag keyed by KEM public to detect bit‑rot without secrets.
- Anti‑rollback: header epoch +
.epochsidecar (warn/fail); optional keychain epoch pin (feature‑gated) to fail on rollback. - Process hardening: no coredumps; tracer/ptrace protections; release
panic=abort; sanitized errors by default. - Windows ACL: refuse insecure vault directory (Everyone/Authenticated Users write).
References: docs/CRYPTO_POLICY.md, docs/THREAT_MODEL.md, docs/SECURITY_MODEL.md, docs/VAULT_FORMAT.md.
Documentation map:
- Full CLI reference:
docs/CLI_REFERENCE.md - Technology deep dive:
docs/TECHNOLOGY.md - Vault format:
docs/VAULT_FORMAT.md - Security model (narrative):
docs/SECURITY_MODEL.md - Configuration:
docs/CONFIGURATION.md - Crypto policy:
docs/CRYPTO_POLICY.md - Threat model:
docs/THREAT_MODEL.md
Technology Deep Dive (Essentials)
-
KDF (Argon2id)
- Defaults: time=10; lanes=max(4, min(8, CPU cores)); mem configurable (suggest 256 MiB)
- Salt=32B from OsRng; output KEK=32B (Zeroizing)
-
KEM (ML‑KEM‑1024) via
pqcrypto-mlkem- Sizes: PK=1568, SK=3168, CT=1568, SS=32 (checked at runtime)
- Decapsulation secret (SK) sealed under KEK; shared secret derived at unlock
-
AEAD (XChaCha20‑Poly1305, 24B nonces)
- AAD labels (exact):
- Payload:
black-bag::payload - Sealed DEK:
black-bag::sealed-dek - Sealed decapsulation:
black-bag::sealed-dk - Sealed rDEK:
black-bag::record-dek - Secret field blob:
black-bag::record-secret
- Payload:
- AAD labels (exact):
-
Key hierarchy
- Passphrase → Argon2id → KEK
- KEK decrypts decapsulation secret (ML‑KEM‑1024 SK)
- SK + (PK, CT) → shared (decaps) → decrypt DEK
- DEK decrypts payload; per‑record rDEKs wrap secret fields
-
Header MAC (keyed BLAKE3)
- Context:
black-bag header mac; verified constant‑time before any decryption - Covers: created/updated, Argon params+salt, epoch, KEM public+ciphertext, sealed blobs (nonces+ciphertexts)
- Context:
-
Integrity & Anti‑rollback
- Public integrity
.int: keyed Blake3 over serialized CBOR with contextblack-bag public integrity - Epoch
.epoch: last seen epoch; warn/fail if header epoch behind sidecar; optional keychain epoch pin (feature‑gated)
- Public integrity
-
Padding & secret blobs
- Optional payload padding: magic
BBPAD1\0\0+ length (LE u64) + CBOR + random pad (block set byBLACK_BAG_PAD_BLOCK) - Secret field blob at rest:
BBE1 | 24B nonce | ciphertext(tag)under rDEK with AADrecord-secret
- Optional payload padding: magic
-
Shamir (recovery)
- GF(2^8): constant‑time
gf_mul; fixed addition‑chaingf_inv - Framed share:
[id | 16B salt | payload | 32B HMAC‑SHA256] - MAC/commit contexts: normal
black-bag share mac/commit; duressblack-bag share mac duress/duress commit - Share‑set commit (base32) verified on combine; legacy (id|payload) still accepted
- GF(2^8): constant‑time
-
Process & memory hygiene
- Zeroization on drop; best‑effort page‑locking; TTY‑only reveals by default; atomic 0600 writes; file/dir sync
- Linux:
O_NOATIMEreads; undumpable + tracer detection; macOS: deny attach; Windows: ACL checks
-
Limits and families
- Vault file ≤ 64 MiB; payload plaintext ≤ 32 MiB; secret field ≤ 8 KiB; notes/keys ≤ 256 KiB
- Records ≤ 100k; tags/record ≤ 64; tag length ≤ 128; title ≤ 256; URL ≤ 4096; username ≤ 2048
- Families: login, contact, id, note, bank, wifi, api, wallet, totp, ssh, pgp, recovery
Configuration
- Env vars:
BLACK_BAG_VAULT_PATH,BLACK_BAG_VAULT_DURESS_PATH,BLACK_BAG_UNSAFE_STDOUT,BLACK_BAG_REQUIRE_MLOCK,BLACK_BAG_EMIT,BLACK_BAG_AGENT,BLACK_BAG_UNSAFE_CLIPBOARD,BLACK_BAG_DURESS,BLACK_BAG_HARD_MODE,BLACK_BAG_STRICT_ROLLBACK,BLACK_BAG_ALLOW_ROLLBACK(see docs for details). - Features: default
mlock,pq; optionaltui,agent-keychain,clipboard,fuzzing.
Troubleshooting
- “vault not initialized” → run
initor setBLACK_BAG_VAULT_PATH. - “header integrity check failed” → stop; header was altered (rollback/tamper). Restore last known good.
- Windows “insecure vault directory permissions” → restrict ACL to current user only.
- TOTP QR warn → requires
--confirm-qr. - Clipboard unavailable → not compiled or
--unsafe-clipboardnot set.
Mission‑Ready Checklist
- Argon2id + ML‑KEM‑1024 + XChaCha20‑Poly1305 enabled by default
- TTY‑only reveals; no stdout secrets by default
- Strict input caps; atomic writes; zeroized buffers
- Cross‑platform (macOS/Linux/Windows)
- Comprehensive record families; search/tagging
- Tests green; warnings denied
- Operator docs + threat model
For production roll‑out, schedule an independent audit and set up long‑run fuzzing (see docs/FURTHER_HARDENING.md).
Recent Security Changes
- Removed CLI secret parameters (prompt/stdin/file only)
- Constant‑time Shamir GF operations; sanitized errors
- Migrated to ML‑KEM‑1024; runtime size checks
- Input bounds added (pre/post parse caps)
- Argon2 defaults increased (time=10; lanes auto≥4)
Backups & Integrity
backup verify --path <vault.cbor> [--pub-key <ed25519.pub>]checks the public integrity sidecar (.int) and, if a public key is provided, verifies the detached signature sidecar (.int.sig). This checks bit‑rot, not authenticity unless a signature is supplied; keep vault and.intpaired.backup sign --path <vault.cbor> --key <ed25519.sk> [--pub-out <ed25519.pub>]signs the integrity tag with an operator‑managed Ed25519 key.
Recovery (Audit‑proof & Human‑proof)
What Changed (Recent)
- Added
record edit/deletecommands for operator ergonomics. - Expanded
export csvwith per‑family schemas and--schemaheader‑only mode. get --otpauth(text/JSON) and ASCII QR for TOTP provisioning;totp doctorfor skew/TTL diagnostics.scan passwordsfinds duplicates and weak (zxcvbn<3) offline; JSON via--format json.recovery split --threshold N --shares M [--with-checksum] [--qr --confirm-qr] [--duress]printsid-<base64>tokens; optional checksum is appended as a trailing comment and QR codes can be printed for human transfer.recovery verify --threshold N --shares "1-...,2-..." [--set-id ...] [--duress]printsOK,mismatch, ortamperwithout emitting any secret.recovery doctor ...detects legacy‑only / mixed sets and prints exact remediation (what to re‑split).