vault unlock --idle counts the duration from the last command that used the key rather than from the unlock: the agent resets its timer each time it hands the key out. The agent loop no longer needs its own goroutine. packaging/repo.sh builds the dixon-vault .deb, .rpm and Arch packages from a release tag (a clean git archive) with nfpm for amd64, arm64, armv7, 386 and riscv64, then publishes them to the apt, dnf and pacman repositories at repo.dixon.cx: apt-ftparchive with a signed InRelease, createrepo_c (in a Fedora container) with signed RPMs and repomd.xml, and repo-add (in an Arch container) with signed packages and database. Indexes cover every package in the repository, under a lock, so other projects publishing with their copy of the script are kept. The package is dixon-vault because vault is HashiCorp Vault in Arch's [extra] and HashiCorp's own repositories; it conflicts with vault. README, guide and CLAUDE.md point at the repository.
3.6 KiB
3.6 KiB
CLAUDE.md
This file provides guidance to Claude Code when working with this repository.
Overview
vault is a post-quantum encrypted file vault: one package main at the repo
root that builds a single vault binary. Module github.com/andydixon/vault.
main.go- CLI:genkey,init,add(file, directory or URL),migrate(add, then delete originals),mirror,absorb(tar on stdin,absorb.go),ls(one folder level),list(recursive),get(-rfor a folder),mv/cp(files or folders;transfersresolves names like mv(1), copies share objects),rm(-rfor a folder, asks unless-f),unlock/lock.agent*.go-vault unlockre-executes itself as a hidden__agentprocess holding the suite ID and seed (pipe in, mlocked, undumpable) and serving them on a Unix socket in a 0700 per-user dir until a deadline (fixed, or with--idlereset on each use).keyIdentityasks it first unless$VAULT_PASSPHRASEis set.keyfile.go- key file with two passphrase slots (Argon2id + XChaCha20-Poly1305 sealing a suite ID and 32-byte master seed). The second slot is either a decoy identity or random bytes; slot order is random.store.go- on-disk vault:records/holds fixed-size encrypted catalogue records,objects/holds file bodies cut into 4 MiB chunks (last one bucket-padded) whose IDs are HMAC'd from a per-record seed. Both are sharded into 256xx/subdirectories by ID prefix. Records a key cannot open are skipped, not treated as errors.lock*.go- vault lock onDIR/.lock: fcntl on unix, LockFileEx on Windows, an exclusive lock file elsewhere. Writers lock exclusively, readers shared, taken after the passphrase prompt.docs/guide- LaTeX user guide (build-pdf.shvia Tectonic;shots.pyre-shoots screenshots from the real binary). Keep it in step with the CLI.packaging/repo.sh [TAG]- builds dixon-vault .deb/.rpm/Arch packages from a tag with nfpm and publishes them, signed, to the apt/dnf/pacman repo at repo.dixon.cx (same script, other project block, in ../mapsize).build.sh- cross-compiles everygo tool dist listtarget except iOS and android/386, amd64, arm (cgo-only).internal/pqcrypt- file encryption (header + STREAM ChaCha20-Poly1305), suite registry, hybrid FrodoKEM-640 + ML-KEM-768 default suite.internal/format,internal/stream,internal/term- header encoding, chunked payload encryption, terminal passphrase prompts.
Commands
go test ./...
go build .
./build.sh # all platforms, into ./dist
TARGETS="linux/amd64" ./build.sh # a subset
End-to-end check:
export VAULT_PASSPHRASE=test VAULT_KEY=$PWD/k VAULT_DIR=$PWD/s
./vault genkey && ./vault init && ./vault add README.md docs && ./vault list
Invariants
- Never write a seed or private key unencrypted (the agent gets it by pipe).
- Keep suite IDs, stanza tags, HKDF info strings and
fileKeySize(32) stable. - Every ciphertext's and shard directory's mtime is pinned to the epoch; records stay one fixed size; objects stay at most 4 MiB and bucket-padded. These hide metadata from the storage host.
- An object's plaintext starts with its own ID, binding it to its record and its position in the file.
getpublishes output only after every object has authenticated.- Decoy deniability: both key slots stay the same size and are both always
tried;
initalways writes filler; foreign records are skipped silently. - Every command that changes the vault holds the exclusive lock.
- Keep dependencies pure Go and CGO_ENABLED=0 builds working.
- Diagnostics go to standard error, lower-case, no trailing full stops.