secret.IdentityToLockedBuffer (in internal/secret/crypto.go) is now the one place an age identity's private key goes into a locked buffer. All eight places that did it themselves use it: the vault's long-term key when a passphrase, PGP, keychain or Secure Enclave unlocker is created, each new unlocker's own key, each new secret version's key, and the key secret encrypt generates.
It moves the bytes of the string age returns into the buffer; memguard wipes what it moves, so that string is overwritten. Go otherwise forbids changing a string, hence unsafe; age builds a fresh string on every call and keeps none, and the new test checks the identity still gives the same key afterwards. The copies age makes while encoding the key remain, as its comment says. Encoding the key straight into locked memory was not attempted.
memguard v0.22.5 LockedBuffer.String() points into the locked memory rather than copying (checked in its source), so the remaining .String() calls in internal/cli/crypto.go are not copies.
The Secure Enclave unlocker has no site of its own any more: it gets the long-term key from deriveLongTermPrivateKey in keychainunlocker.go. That file is compiled, vetted and linted for macOS without cgo by the macOS lint from #50; it is not run anywhere.
Existing tests create, then unlock with, a passphrase unlocker (TestPassphraseUnlockerReplacementKeepsVaultOpen) and a PGP unlocker with a real gpg key (TestAddPGPUnlocker).
Rule suppressed: gosec G103 (unsafe) on that one line.
Judgement call: overwriting the string, rather than a plain copy that would leave it as before.
Model: opus-5-5
For https://git.eeqj.de/sneak/secret/issues/38.
`secret.IdentityToLockedBuffer` (in `internal/secret/crypto.go`) is now the one place an age identity's private key goes into a locked buffer. All eight places that did it themselves use it: the vault's long-term key when a passphrase, PGP, keychain or Secure Enclave unlocker is created, each new unlocker's own key, each new secret version's key, and the key `secret encrypt` generates.
It moves the bytes of the string age returns into the buffer; memguard wipes what it moves, so that string is overwritten. Go otherwise forbids changing a string, hence `unsafe`; age builds a fresh string on every call and keeps none, and the new test checks the identity still gives the same key afterwards. The copies age makes while encoding the key remain, as its comment says. Encoding the key straight into locked memory was not attempted.
memguard v0.22.5 `LockedBuffer.String()` points into the locked memory rather than copying (checked in its source), so the remaining `.String()` calls in `internal/cli/crypto.go` are not copies.
The Secure Enclave unlocker has no site of its own any more: it gets the long-term key from `deriveLongTermPrivateKey` in `keychainunlocker.go`. That file is compiled, vetted and linted for macOS without cgo by the macOS lint from https://git.eeqj.de/sneak/secret/issues/50; it is not run anywhere.
Existing tests create, then unlock with, a passphrase unlocker (`TestPassphraseUnlockerReplacementKeepsVaultOpen`) and a PGP unlocker with a real gpg key (`TestAddPGPUnlocker`).
- Rule suppressed: gosec G103 (`unsafe`) on that one line.
- Judgement call: overwriting the string, rather than a plain copy that would leave it as before.
Model: opus-5-5
secret.IdentityToLockedBuffer replaces the eight places that converted
an age identity's String() to bytes for a locked buffer and left the
string, which holds the private key, in ordinary memory. It moves the
string's own bytes into the buffer, which overwrites them. The copies
age makes while encoding the key remain; the function's comment says
so. TODO.md drops these places from the 1.0 memory-security entry,
along with its stale version.go reference.
Model: opus-5-5
PASS: secret.IdentityToLockedBuffer is the one place an age identity's private key goes into a locked buffer, every site in non-test code uses it with the buffer destroyed on every path, its limitation is written at the function, and #38 is done as defined.
Model: opus-5-5
PASS: `secret.IdentityToLockedBuffer` is the one place an age identity's private key goes into a locked buffer, every site in non-test code uses it with the buffer destroyed on every path, its limitation is written at the function, and https://git.eeqj.de/sneak/secret/issues/38 is done as defined.
Model: opus-5-5
clawbot
merged commit ef79111e2e into next2026-10-04 18:42:01 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
For #38.
secret.IdentityToLockedBuffer(ininternal/secret/crypto.go) is now the one place an age identity's private key goes into a locked buffer. All eight places that did it themselves use it: the vault's long-term key when a passphrase, PGP, keychain or Secure Enclave unlocker is created, each new unlocker's own key, each new secret version's key, and the keysecret encryptgenerates.It moves the bytes of the string age returns into the buffer; memguard wipes what it moves, so that string is overwritten. Go otherwise forbids changing a string, hence
unsafe; age builds a fresh string on every call and keeps none, and the new test checks the identity still gives the same key afterwards. The copies age makes while encoding the key remain, as its comment says. Encoding the key straight into locked memory was not attempted.memguard v0.22.5
LockedBuffer.String()points into the locked memory rather than copying (checked in its source), so the remaining.String()calls ininternal/cli/crypto.goare not copies.The Secure Enclave unlocker has no site of its own any more: it gets the long-term key from
deriveLongTermPrivateKeyinkeychainunlocker.go. That file is compiled, vetted and linted for macOS without cgo by the macOS lint from #50; it is not run anywhere.Existing tests create, then unlock with, a passphrase unlocker (
TestPassphraseUnlockerReplacementKeepsVaultOpen) and a PGP unlocker with a real gpg key (TestAddPGPUnlocker).unsafe) on that one line.Model: opus-5-5
PASS:
secret.IdentityToLockedBufferis the one place an age identity's private key goes into a locked buffer, every site in non-test code uses it with the buffer destroyed on every path, its limitation is written at the function, and #38 is done as defined.Model: opus-5-5