Slice 1 of #154: the store, the CLI, and the settings form.
What it adds
forge/secret: one encrypted value per name per repository, indata/secrets.db. Each value is AES-256-GCM with the repository id, name and version as additional data, so a row copied to another name or replayed as an older version does not open. The key isdata/secrets.key(0600, created on first use, movable withsecrets_key).- Rows are keyed by the repository’s stable id, so a rename keeps its secrets and a new repository that takes the old name inherits nothing. Deleting a repository deletes its values, through the same two-step the access rules use.
vole secret list|set|get|rmover the SSH verbvole-secret. The value of asettravels on stdin, never in the exec line, which both hosts can see.- The same list on the repository’s Settings tab: names, versions, who changed one, when a value was last read, with set, replace and delete. No page renders a value, so the form is the only half the browser gets.
- Every read and change is a line in
data/audit.log; a read that cannot be recorded is refused, the same as an access change.
Decisions worth reviewing
- A maintainer may now open Settings. The page already hid its admin-only
forms behind
RepoAdmin; it is the one place the secrets belong, and duplicating it as a second page would have needed a second tab. The two tests that asserted “maintainer settings = 404” now assert that the page is reachable and that the admin fields are absent. - 128 KiB per value, which is the kernel’s limit for one
NAME=valuestring inenvp. A longer value could never reach a step. Valid UTF-8 and no NUL are required because the value will travel as JSON over the lease channel and then into a process environment. - No per-name redaction of values in the audit detail. The detail carries the name and the version; the value is the one thing that must never reach the log, and the store tests assert it does not.
getadds no newline, so a redirect to a file produces the stored bytes. The CLI strips one trailing newline from a piped value only, becauseecho token | vole secret set …is the obvious way to store one.
Not in this change
Slice 2 (secrets: in the workflow, lease delivery, the absent-secret warning)
and slice 3 (log masking, the record’s {name, version}, the remaining doc
sweep). The settings copy and the CLI help therefore describe what exists now,
not the workflow field.
Checks
go vet ./...,GOFLAGS=-tags=sqlite_fts5 go test ./...,vole workflow check- New tests: the store (round trip, isolation by repository, row binding, key mismatch, key mode, the value rules, multi-line values, audit) and the CLI and page end to end, including that a non-maintainer is refused and that a page never contains a value.