Filesystem basics: mv now moves folders as well as files, and like mv(1) a NEW ending in / or naming an existing folder means into it. cp copies a file, or a folder with -r, by writing new records that share the original's objects, so it is instant and takes no space; rm already keeps objects another record still uses. New records go first, so an interrupted move leaves both names, never neither. get -r extracts a folder, keeping its structure. vault unlock [--for 5m] checks the passphrase and starts the binary again as a hidden __agent process, handing it the suite ID and seed over a pipe. The agent keeps them in mlocked memory, with core dumps off and (on Linux) PR_SET_DUMPABLE cleared, serves them on a Unix socket in a 0700 per-user directory whose owner is checked on every use, and wipes them and exits at a hard deadline or on vault lock. Commands ask it first unless VAULT_PASSPHRASE is set, and genkey stops any agent left for that path. Guide, man pages (new vault-cp, vault-unlock, vault-lock), README and screenshots updated; the guide's error table is split so it fits.
71 lines
3.4 KiB
Markdown
71 lines
3.4 KiB
Markdown
# 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` (`-r` for a folder), `mv`/`cp` (files or folders; `transfers`
|
|
resolves names like mv(1), copies share objects), `rm` (`-r` for a
|
|
folder, asks unless `-f`), `unlock`/`lock`.
|
|
- `agent*.go` - `vault unlock` re-executes itself as a hidden `__agent`
|
|
process 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 hard
|
|
deadline. `keyIdentity` asks it first unless `$VAULT_PASSPHRASE` is 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 256 `xx/` subdirectories by ID prefix. Records a key cannot
|
|
open are skipped, not treated as errors.
|
|
- `lock*.go` - vault lock on `DIR/.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.sh` via Tectonic; `shots.py`
|
|
re-shoots screenshots from the real binary). Keep it in step with the CLI.
|
|
- `build.sh` - cross-compiles every `go tool dist list` target 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
|
|
|
|
```sh
|
|
go test ./...
|
|
go build .
|
|
./build.sh # all platforms, into ./dist
|
|
TARGETS="linux/amd64" ./build.sh # a subset
|
|
```
|
|
|
|
End-to-end check:
|
|
|
|
```sh
|
|
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.
|
|
- `get` publishes output only after every object has authenticated.
|
|
- Decoy deniability: both key slots stay the same size and are both always
|
|
tried; `init` always 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.
|