# Workflow * branch (from `main`) * do the work in Next Step * move Next Step to the top of Completed Steps * move the top item of Future Steps into Next Step * commit (`TODO.md` changes in the same commit as the work) * merge to `main` if the branch is not protected, otherwise open a PR * push # Status pre-1.0. No git tags. TODO.md carries open 1.0 security blockers. Work in flight on branch secure-enclave-unlocker (clean tree as of 2026-07-06). # Next Step Bring the repo into policy compliance in one commit: - Add fmt-check and hooks targets to the Makefile (test/lint/fmt/check/ docker already exist). - Add REPO_POLICIES.md and .editorconfig. - Add .gitea/workflows/check.yml running make check. - Verify Dockerfile base images are pinned by sha256. # Completed Steps - 2026-10-04: An age identity's private key goes into a locked buffer through `secret.IdentityToLockedBuffer` everywhere (https://git.eeqj.de/sneak/secret/issues/38): the vault's long-term key when a passphrase, PGP, keychain or Secure Enclave unlocker is created, the new unlocker's own key, a new secret version's key, and the key `secret encrypt` generates. Before, each place converted the string age returns to bytes and left the string in ordinary memory. The function moves the string's own bytes into the buffer, which overwrites them; the copies age makes while writing the string remain, as its comment says. The 1.0 memory-security entry below no longer lists these places, `internal/cli/crypto.go` among them, nor `version.go:155`, which was `internal/secret/version.go`, not `internal/cli/version.go`. - 2026-10-04: `script/lint-darwin` (`make lint-darwin`) runs `go vet` and `golangci-lint` in docker on the code as a macOS build compiles it (`GOOS=darwin`), with cgo off (https://git.eeqj.de/sneak/secret/issues/50). `script/check` runs it, and the `Dockerfile` lint stage runs its commands, so `script/cibuild` does too. Before, CI on Linux never compiled the files built only for macOS. Compiling cgo code for macOS needs Apple's SDK headers, and both `internal/macse` and `github.com/keybase/go-keychain` are cgo on macOS. So the three functions that call `go-keychain` moved from `keychainunlocker.go` to `keychainunlocker_cgo.go`, built only with cgo on macOS like `macse_darwin.go`. A macOS build without cgo, which before did not compile, gets `keychainunlocker_nocgo.go` and the `macse` stub instead, whose errors say the keychain or Secure Enclave needs a macOS build with cgo. The check covers the rest of the keychain unlocker, the Secure Enclave unlocker and the macOS-only tests other than `keychainunlocker_test.go`, whose lint findings are fixed. For the length and complexity limits, parts of `GetIdentity`, `getLongTermPrivateKey` and `CreateKeychainUnlocker` moved into functions of their own, and the Secure Enclave unlocker derives the long-term key from the mnemonic through the same function as the keychain unlocker instead of a copy of it. Lines over 88 columns in the files the check cannot see are wrapped. - 2026-10-04: `secret rm`, `secret version rm`, `secret vault remove` and `secret unlocker remove` ask `[y/N]` before removing anything (https://git.eeqj.de/sneak/secret/issues/39), naming what they remove: the secret, its vault and its version count; the version, secret and vault; the vault and its secret count; the unlocker, its vault and whether it is the last, and for the last the vault's secret count and that the vault then opens only with its mnemonic. Only `y` or `yes` goes ahead. Without `--force`, a command whose stdin is not a terminal fails at once. `--force` (now also on `rm` and `version rm`) removes without asking; it replaces the old refusals to remove a vault with secrets or the last unlocker of one without `--force`, which the question now covers. The checks run, and the question is asked, before the state directory lock is taken; under the lock the checks run again, and if they would ask a different question, nothing is removed. `secret rm` fails when it cannot count the versions. - 2026-10-04: A crash while an unlocker is being replaced no longer leaves a current unlocker that cannot open the vault (https://git.eeqj.de/sneak/secret/issues/71). Every new unlocker gets a directory of its own, named with the time to the nanosecond: `passphrase-