fix: close server bypass edge cases

This commit is contained in:
2026-08-05 12:09:32 +02:00
parent a94affd6ac
commit fc349e634c
9 changed files with 296 additions and 148 deletions
+1 -1
View File
@@ -175,7 +175,7 @@ func serverOperationFailure(stderr io.Writer, err error, secretValues []string)
message := output.Sanitize(err.Error(), secretValues)
var operationErr *serverops.OperationError
if errors.As(err, &operationErr) && operationErr.Detail() != "" {
detail := output.Sanitize(operationErr.Detail(), secretValues)
detail := output.SanitizeDetail(operationErr.Detail(), secretValues)
fmt.Fprintf(stderr, "thothctl: %s: %s\n", message, detail)
} else {
fmt.Fprintf(stderr, "thothctl: %s\n", message)
+51 -25
View File
@@ -104,33 +104,59 @@ func TestRunSessionsMigrateRequiresExplicitConfirmationBeforeDocker(t *testing.T
assertDockerNotInvoked(t, fixture)
}
func TestRunSessionsMigrateReportsSanitizedStageAndExitClass(t *testing.T) {
fixture := newCLIFixture(t, "MIGRATION_PASSWORD_FILE=%s\n")
fixture.setProfile(t, "server")
secretPath := filepath.Join(fixture.root, "migration-password")
if err := os.WriteFile(secretPath, []byte("migration-secret-value"), 0o600); err != nil {
t.Fatal(err)
}
fixture.setEnvironment(t, secretPath)
t.Setenv("THOTHCTL_FAKE_CONFIG", `{"services":{"core":{"image":"thothii-core:local"},"session-migrate":{"image":"thothii-core:local"}}}`)
t.Setenv("THOTHCTL_FAKE_MIGRATION_FAILURE", "TLS rejected migration-secret-value")
t.Setenv("THOTHCTL_FAKE_MIGRATION_EXIT", "23")
var stdout, stderr bytes.Buffer
func TestRunSessionsMigrateRedactsCompleteDetailBeforeDisplayBound(t *testing.T) {
longSecret := "long-secret-" + strings.Repeat("s", 700)
for _, spec := range []struct {
name string
secret string
failure string
leakedProbe string
}{
{
name: "secret longer than display limit",
secret: longSecret,
failure: longSecret + " rejected by TLS",
leakedProbe: longSecret[:64],
},
{
name: "secret crossing display boundary",
secret: "boundary-secret-value",
failure: strings.Repeat("p", 500) + "boundary-secret-value rejected",
leakedProbe: "boundary-sec",
},
} {
t.Run(spec.name, func(t *testing.T) {
fixture := newCLIFixture(t, "MIGRATION_PASSWORD_FILE=%s\n")
fixture.setProfile(t, "server")
secretPath := filepath.Join(fixture.root, "migration-password")
if err := os.WriteFile(secretPath, []byte(spec.secret), 0o600); err != nil {
t.Fatal(err)
}
fixture.setEnvironment(t, secretPath)
t.Setenv("THOTHCTL_FAKE_CONFIG", `{"services":{"core":{"image":"thothii-core:local"},"session-migrate":{"image":"thothii-core:local"}}}`)
t.Setenv("THOTHCTL_FAKE_MIGRATION_FAILURE", spec.failure)
t.Setenv("THOTHCTL_FAKE_MIGRATION_EXIT", "23")
var stdout, stderr bytes.Buffer
code := run(context.Background(), []string{
"--installation", fixture.installationPath, "sessions", "migrate", "--yes",
}, &stdout, &stderr)
code := run(context.Background(), []string{
"--installation", fixture.installationPath, "sessions", "migrate", "--yes",
}, &stdout, &stderr)
if code != 1 {
t.Fatalf("exit = %d, stderr = %q", code, stderr.String())
}
for _, required := range []string{"stage=session-migration", "class=nonzero-exit", "TLS rejected [REDACTED]"} {
if !strings.Contains(stderr.String(), required) {
t.Errorf("stderr = %q, missing %q", stderr.String(), required)
}
}
if strings.Contains(stderr.String(), "migration-secret-value") {
t.Fatalf("stderr exposed secret: %q", stderr.String())
if code != 1 {
t.Fatalf("exit = %d, stderr = %q", code, stderr.String())
}
for _, required := range []string{"stage=session-migration", "class=nonzero-exit", "[REDACTED]"} {
if !strings.Contains(stderr.String(), required) {
t.Errorf("stderr = %q, missing %q", stderr.String(), required)
}
}
if strings.Contains(stderr.String(), spec.secret) || strings.Contains(stderr.String(), spec.leakedProbe) {
t.Fatalf("stderr exposed secret or prefix: %q", stderr.String())
}
if stderr.Len() > 640 {
t.Fatalf("stderr exceeded bounded display: %d bytes", stderr.Len())
}
})
}
}