Lock the state directory and write vault files atomically (closes #34)
check / check (push) Successful in 50s

Each command that changes the state directory holds one lock: flock(2)
on `lock` in the state directory, dropped by the kernel if the process
dies, or a process-wide mutex on the in-memory test filesystem. It
covers the state directory, not each vault, because `currentvault`,
`vault create` and cross-vault moves span vaults, and a lock file in a
vault would be deleted by `vault remove` under a waiting command.

Files go through `secret.WriteFileAtomic`; versions, new secrets and
cross-vault copies are built in a temporary directory and renamed into
place; removals rename out of the way first. An unlocker added under
an existing unlocker's directory name is still rewritten file by file:
#71.

Model: opus-5-5
This commit is contained in:
2026-10-03 14:41:24 +00:00
committed by sneak
parent d52b4f1240
commit c761670cb0
24 changed files with 1381 additions and 187 deletions
+15 -2
View File
@@ -25,6 +25,21 @@ Bring the repo into policy compliance in one commit:
# Completed Steps
- 2026-10-03: Commands that change the state directory hold one lock
(`flock` on `lock` in the state directory; a mutex on the in-memory
test filesystem), so concurrent commands no longer lose versions or
race on the current pointers. Every file is written through
`secret.WriteFileAtomic` (temporary file, sync, rename); new
versions, new secrets and cross-vault copies are built in a
temporary directory and renamed into place, and removals rename out
of the way first, so an interrupted command leaves nothing
half-written, with one exception: an unlocker added under the
directory name of an existing one is rewritten in place, file by
file, and a crash part-way leaves it unable to open the vault. That
happens to a passphrase unlocker added to a vault that has one, and
to a PGP, keychain or Secure Enclave unlocker added on the same host
and day as another of its type
(https://git.eeqj.de/sneak/secret/issues/71).
- 2026-10-02: A plain `docker build .` builds again: the size tests
skip a case that needs more locked memory than the process can
lock, and run every case under `script/cibuild`. The image stamps the
@@ -94,8 +109,6 @@ Bring the repo into policy compliance in one commit:
pgpunlocker.go:256, version.go:155); age secret key held in a
plain string in cli/crypto.go:86,91,113; private keys exposed via
buffer.Bytes() to GPGEncryptFunc and EncryptWithPassphrase.
- Race conditions: no file locking in vault/secrets.go:142-176;
non-atomic writes can leave the vault inconsistent.
- Input validation: dots in secret names risk path traversal
(vault/secrets.go:75-99); no maximum secret size (DoS).
- Timing attacks: bytes.Equal passphrase compare (cli/init.go: