# 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: What a command killed part-way left under a `.tmp-` name (https://git.eeqj.de/sneak/secret/issues/75), the temporary directories of `secret.TempDirFor` and the temporary files of `secret.WriteFileAtomic`, encrypted keys included, is deleted by the next command that takes the state directory lock. Before, it stayed until deleted by hand. A command writes `finished` into the lock file just before it releases the lock; the next one to take the lock searches only when it does not find that, so after a command that finished nothing is searched, however many secrets and versions there are. The search looks in the state directory, each vault, each secret and each version, the only directories those helpers make them in. A command that only reads takes no lock and deletes nothing. A failure to delete is warned about and the command goes on. An unlocker directory with no metadata file was already removed by `secret unlocker remove` given its directory name; a test now shows it. - 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-