PGPUnlocker.GetID panics on corrupt metadata, and setMnemonicEnv re-exports the mnemonic #42
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Two related robustness and exposure defects from the 1.0 security survey. Both are small and both are in the "a bad state gets worse" category.
1. Panic on corrupt metadata
internal/secret/pgpunlocker.go:159-169—GetID()callspanic(fmt.Sprintf("PGP unlocker metadata is corrupt or missing GPG key ID: %v", err)).The comment at
:163-164argues the panic is intentional, on the grounds that a fallback ID would mask data corruption. The goal is right; the mechanism is wrong.GetID()is called from listing paths, so a single corrupt unlocker directory takes downsecret unlocker listentirely — the user cannot even enumerate their unlockers to discover which one is broken, let alone remove it. A panic also prints a Go stack trace, which is not a usable error message, and it bypasses every deferredLockedBuffer.Destroy()in the process (see #35).The equivalent code in
keychainunlocker.gowas already fixed:GetID()(:163-176) no longer reads metadata and degrades to"unknown". This one was missed.Worth noting the precedent: #1 in this tracker was the same class of bug — a single unlocker with missing metadata broke the
listcommand — and was fixed by skipping the bad directory with a warning. That is the established pattern in this repo.The only other panic in non-test code is
pkg/bip85/bip85.go:87, which guards a genuine internal invariant and is fine.2.
setMnemonicEnvputs the mnemonic back into the environmentinternal/cli/vault.go:235sets the mnemonic into the process environment for the duration of vault creation, with a restore closure that unsets it at:241. Thatos.Unsetenvis the only one in non-test code, and the net effect of the helper is to increase exposure, not reduce it: for the duration of the call the mnemonic — the root secret from which every vault key derives — is readable in/proc/<pid>/environby any same-uid process and is inherited by every child process spawned in that window, which includesgpg.More broadly,
SB_SECRET_MNEMONICandSB_UNLOCK_PASSPHRASEare read in a dozen places and never cleared. The README (:81-82,:305-309) documents them as conveniences that avoid the interactive prompt, with no warning that they are visible to same-uid processes, inherited by children, and routinely captured in shell history and CI logs.Definition of done
GetID()returns an error, or degrades to a clearly-marked placeholder the way the keychain unlocker does, instead of panicking. Whichever is chosen, a corrupt unlocker directory must not preventsecret unlocker listfrom listing the others.secret.Warn()mechanism, naming the directory, so it is diagnosable and removable.setMnemonicEnvno longer round-trips the mnemonic through the process environment. Pass it in memory instead — a*memguard.LockedBufferargument through the call chain.gpg, and end up in shell history and CI logs — with the interactive prompt named as the safer default.make checkgreen.TODO.mdupdated in the same commit.Implementation requirements
/procexposure, which is the dominant risk, but do not describe it in comments or docs as if it wipes the value.