Compare commits

..
1 Commits
Author SHA1 Message Date
sneak 271c26e78a Wipe memguard buffers on every exit and restore echo (closes #35)
check / check (push) Successful in 47s
Entry() now returns the exit code and only main calls os.Exit, so the
deferred memguard.Purge() in Entry() runs on success and on error;
before, os.Exit(1) skipped every deferred Destroy().

SIGINT and SIGTERM go through memguard's handler, which wipes every
buffer and exits with status 1. The passphrase prompt turns terminal
echo off until its read returns, and the handler exits before that, so
the handler first restores the terminal settings saved at startup, but
only when this process is in the terminal's foreground process group: a
background process that changes the terminal is stopped instead of
exiting.

Model: opus-5-5
2026-10-03 14:45:12 +00:00
2 changed files with 14 additions and 12 deletions
+4 -3
View File
@@ -28,9 +28,10 @@ Bring the repo into policy compliance in one commit:
- 2026-10-03: Key material is wiped on every exit: `Entry()` returns
the exit code after its deferred `memguard.Purge()` has run, and only
`main` calls `os.Exit`. SIGINT and SIGTERM go through memguard's
handler, which wipes every buffer before exiting; on Ctrl-C it first
restores the terminal settings from startup, so an interrupted
passphrase prompt no longer leaves echo off.
handler, which wipes every buffer before exiting; when the process is
in the terminal's foreground process group it first restores the
terminal settings from startup, so an interrupted passphrase prompt no
longer leaves echo off.
- 2026-10-02: A plain `docker build .` builds again: the size tests
skip a case that needs more locked memory than the process can
lock, and run every case under `script/cibuild`. The image stamps the
+10 -9
View File
@@ -2,11 +2,11 @@ package cli
import (
"os"
"syscall"
"git.eeqj.de/sneak/secret/internal/secret"
"github.com/awnumar/memguard"
"github.com/spf13/cobra"
"golang.org/x/sys/unix"
"golang.org/x/term"
)
@@ -16,16 +16,17 @@ import (
func Entry() int {
// On SIGINT or SIGTERM memguard runs this function, wipes every buffer
// and exits with status 1. The passphrase prompt turns terminal echo
// off until the read finishes, so Ctrl-C there would leave echo off.
// Ctrl-C means this process is in the terminal's foreground and may
// reset it; doing that from the background would stop the process.
terminalState, terminalErr := term.GetState(syscall.Stdin)
// off until the read finishes, so a signal there would leave echo off.
// Only a process in the terminal's foreground process group may reset
// it: one in the background that tries is stopped instead of exiting.
terminalState, terminalErr := term.GetState(unix.Stdin)
memguard.CatchSignal(func(sig os.Signal) {
if sig == os.Interrupt && terminalErr == nil {
_ = term.Restore(syscall.Stdin, terminalState)
memguard.CatchSignal(func(os.Signal) {
foreground, err := unix.IoctlGetInt(unix.Stdin, unix.TIOCGPGRP)
if terminalErr == nil && err == nil && foreground == unix.Getpgrp() {
_ = term.Restore(unix.Stdin, terminalState)
}
}, os.Interrupt, syscall.SIGTERM)
}, os.Interrupt, unix.SIGTERM)
defer memguard.Purge()