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
@@ -45,8 +45,8 @@ const (
ExitClassInvocation ExitClass = "invocation-failure"
)
// OperationError reports only allowlisted operation metadata from Error. Detail remains bounded
// and is exposed separately so the CLI can redact declared secrets before displaying it.
// OperationError reports only allowlisted operation metadata from Error. Complete subprocess
// detail is exposed separately so the CLI can redact it before applying its display bound.
type OperationError struct {
stage Stage
class ExitClass
@@ -384,24 +384,7 @@ func runDocker(ctx context.Context, runner Runner, stage Stage, args []string) (
if strings.TrimSpace(detail) == "" {
detail = err.Error()
}
return result, &OperationError{stage: stage, class: class, detail: boundedDetail(detail)}
return result, &OperationError{stage: stage, class: class, detail: detail}
}
return result, nil
}
func boundedDetail(detail string) string {
detail = strings.Join(strings.Fields(detail), " ")
const maximumBytes = 512
if len(detail) <= maximumBytes {
return detail
}
var bounded strings.Builder
for _, character := range detail {
encoded := string(character)
if bounded.Len()+len(encoded) > maximumBytes {
break
}
bounded.WriteString(encoded)
}
return bounded.String()
}
@@ -131,38 +131,52 @@ func TestMigrateSessionsFailsClosedBeforeMutation(t *testing.T) {
}
}
func TestMigrateSessionsReturnsTypedBoundedRunFailure(t *testing.T) {
installation := testInstallation(t)
secret := "database-password-in-stderr"
configCalls := 0
runner := &fakeRunner{run: func(args []string) (compose.Result, error) {
switch {
case contains(args, "ps", "--all"):
return compose.Result{Stdout: `[]`}, nil
case contains(args, "config", "--format", "json"):
configCalls++
return compose.Result{Stdout: `{"services":{"core":{"image":"thothii-core:local"},"session-migrate":{"image":"thothii-core:local"}}}`}, nil
case contains(args, "run", "--rm", "--no-deps", "--no-TTY", "session-migrate"):
return compose.Result{Stderr: "TLS connection for " + secret + ": " + strings.Repeat("x", 2048), ExitCode: 23}, errors.New("exit status 23")
default:
t.Fatalf("unexpected Docker invocation: %#v", args)
return compose.Result{}, nil
}
}}
func TestMigrateSessionsPreservesCompleteFailureDetailBehindTypedMetadata(t *testing.T) {
longSecret := "long-secret-" + strings.Repeat("s", 700)
for _, spec := range []struct {
name string
secret string
stderr string
}{
{name: "secret longer than display limit", secret: longSecret, stderr: longSecret + " rejected"},
{name: "secret crossing display boundary", secret: "boundary-secret-value", stderr: strings.Repeat("p", 500) + "boundary-secret-value rejected"},
} {
t.Run(spec.name, func(t *testing.T) {
installation := testInstallation(t)
configCalls := 0
runner := &fakeRunner{run: func(args []string) (compose.Result, error) {
switch {
case contains(args, "ps", "--all"):
return compose.Result{Stdout: `[]`}, nil
case contains(args, "config", "--format", "json"):
configCalls++
return compose.Result{Stdout: `{"services":{"core":{"image":"thothii-core:local"},"session-migrate":{"image":"thothii-core:local"}}}`}, nil
case contains(args, "run", "--rm", "--no-deps", "--no-TTY", "session-migrate"):
return compose.Result{Stderr: spec.stderr, ExitCode: 23}, errors.New("exit status 23")
default:
t.Fatalf("unexpected Docker invocation: %#v", args)
return compose.Result{}, nil
}
}}
_, err := MigrateSessions(context.Background(), installation, runner, true)
var operationErr *OperationError
if !errors.As(err, &operationErr) {
t.Fatalf("MigrateSessions() error = %T %v, want OperationError", err, err)
}
if operationErr.Stage() != StageSessionMigration || operationErr.Class() != ExitClassNonzero {
t.Fatalf("operation error = %#v", operationErr)
}
if detail := operationErr.Detail(); !strings.Contains(detail, secret) || len(detail) > 512 {
t.Fatalf("bounded detail length=%d value=%q", len(detail), detail)
}
if configCalls != 2 {
t.Fatalf("config calls = %d", configCalls)
_, err := MigrateSessions(context.Background(), installation, runner, true)
var operationErr *OperationError
if !errors.As(err, &operationErr) {
t.Fatalf("MigrateSessions() error = %T %v, want OperationError", err, err)
}
if operationErr.Stage() != StageSessionMigration || operationErr.Class() != ExitClassNonzero {
t.Fatalf("operation error = %#v", operationErr)
}
if strings.Contains(operationErr.Error(), spec.secret[:12]) {
t.Fatalf("typed metadata exposed secret prefix: %q", operationErr.Error())
}
if detail := operationErr.Detail(); detail != spec.stderr || !strings.Contains(detail, spec.secret) {
t.Fatalf("detail was truncated before redaction: length=%d", len(detail))
}
if configCalls != 2 {
t.Fatalf("config calls = %d", configCalls)
}
})
}
}