security: sanitize token from re-exec log line

os.Args logged during self-update re-exec could expose --token value
in plaintext to log aggregators. sanitizeArgs masks the value.
This commit is contained in:
Graham McIntire 2026-02-12 10:59:08 -06:00
parent a0516c2bd1
commit 442b019b0b
No known key found for this signature in database
2 changed files with 55 additions and 1 deletions

View file

@ -9,6 +9,7 @@ import (
"net/http"
"net/url"
"os"
"strings"
"syscall"
)
@ -78,6 +79,20 @@ func selfUpdate(downloadURL, expectedChecksum string) error {
slog.Info("binary replaced", "path", currentExe)
// Re-exec with same arguments
slog.Info("re-executing", "args", os.Args)
slog.Info("re-executing", "args", sanitizeArgs(os.Args))
return syscall.Exec(currentExe, os.Args, os.Environ())
}
// sanitizeArgs returns a copy of args with token values masked.
func sanitizeArgs(args []string) []string {
out := make([]string, len(args))
copy(out, args)
for i, a := range out {
if (a == "--token" || a == "-token") && i+1 < len(out) {
out[i+1] = "***"
} else if strings.HasPrefix(a, "--token=") || strings.HasPrefix(a, "-token=") {
out[i] = a[:strings.Index(a, "=")+1] + "***"
}
}
return out
}

View file

@ -283,6 +283,45 @@ func TestSelfUpdateTooLarge(t *testing.T) {
}
}
func TestSanitizeArgs(t *testing.T) {
tests := []struct {
name string
in []string
want []string
}{
{"flag with separate value", []string{"agent", "--token", "secret"}, []string{"agent", "--token", "***"}},
{"no sensitive flags", []string{"agent", "--api-url", "wss://x"}, []string{"agent", "--api-url", "wss://x"}},
{"equals syntax", []string{"agent", "--token=secret"}, []string{"agent", "--token=***"}},
{"empty args", []string{"agent"}, []string{"agent"}},
{"trailing flag no value", []string{"agent", "--token"}, []string{"agent", "--token"}},
{"short flag with value", []string{"agent", "-token", "secret"}, []string{"agent", "-token", "***"}},
{"short equals syntax", []string{"agent", "-token=secret"}, []string{"agent", "-token=***"}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := sanitizeArgs(tt.in)
if len(got) != len(tt.want) {
t.Fatalf("length: got %d, want %d", len(got), len(tt.want))
}
for i := range got {
if got[i] != tt.want[i] {
t.Errorf("arg[%d]: got %q, want %q", i, got[i], tt.want[i])
}
}
})
}
// Verify original slice is not mutated
t.Run("does not mutate input", func(t *testing.T) {
orig := []string{"agent", "--token", "secret"}
_ = sanitizeArgs(orig)
if orig[2] != "secret" {
t.Error("sanitizeArgs mutated the input slice")
}
})
}
// rewriteToHTTPS converts an httptest TLS server URL to use the https scheme.
// httptest.NewTLSServer returns URLs with https:// already, but this ensures consistency.
func rewriteToHTTPS(rawURL string) string {