v0.311.0 (before the build): the repair's commands run quietly — no household file name can reach a log or the operator event
gates / gates (push) Successful in 50s

Security review of 8d862e4: compose's shared runner logs a failed command's stdout and puts its
stderr in the error, and that error became the event's `problem`. The after_unclean_boot commands now
run through a quiet runner (nothing logged, stderr discarded) and `problem` keeps the exit code only.
TestAUB_AFailingCommandLeaksNoOutput, red-proofed.

Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
This commit is contained in:
2026-10-14 16:58:20 +02:00
parent 8d862e4e69
commit 3b85a0c29a
3 changed files with 77 additions and 3 deletions
@@ -267,3 +267,25 @@ func TestAUB_DismissHidesTheNoteKeepsTheRecord(t *testing.T) {
t.Fatalf("%+v", r)
}
}
// Security review of 8d862e4: a failing command's output and stderr can hold the household's file names (the lost
// check prints them). They must reach neither the log nor the result's Problem (which goes to the operator event).
// RED-PROOF: return `err` instead of `exitOnly(err)` from aubExec → the stderr name lands in Problem → fails.
func TestAUB_AFailingCommandLeaksNoOutput(t *testing.T) {
m, drive, _, logBuf := aubManager(t)
root := filepath.Join(drive, "appdata/cloudapp/thumbs")
aubWrite(t, filepath.Join(root, "u1", idA+"_thumbnail.webp"), "")
m.afterLoadFn = func(_ string, args ...string) (string, error) {
return "LOST {\"name\":\"IMG_SECRET.jpg\"}\n", errors.New("exit code 1\nstderr: ERR at IMG_SECRET.jpg")
}
res := m.RunAfterUncleanBoot("cloudapp", time.Time{})
if res.Problem == "" {
t.Fatal("a failing run must report a problem")
}
if strings.Contains(res.Problem, "IMG_SECRET") || strings.Contains(logBuf.String(), "IMG_SECRET") {
t.Fatalf("a file name leaked: problem=%q", res.Problem)
}
if !strings.Contains(res.Problem, "exit code 1") {
t.Fatalf("the exit code must stay: %q", res.Problem)
}
}