package logs import ( "bytes" "errors" "io" "strings" "sync" "testing" ) func TestSanitizerStripsANSIAndMasksSecretsAcrossWrites(t *testing.T) { var output bytes.Buffer writer := NewSanitizer(&output, [][]byte{[]byte("top-secret"), []byte("secret")}) parts := []string{ "plain \x1b[31mred\x1b[0m ", "top-", "secret and sec", "ret\n", "link \x1b]8;;https://example.invalid\x1b\\text\x1b]8;;\x1b\\ done", } for _, part := range parts { if _, err := io.WriteString(writer, part); err != nil { t.Fatal(err) } } if err := writer.Close(); err != nil { t.Fatal(err) } if got, want := output.String(), "plain red [MASKED] and [MASKED]\nlink text done"; got != want { t.Fatalf("output=%q want %q", got, want) } } func TestSanitizerDropsIncompleteANSIAndRejectsWritesAfterClose(t *testing.T) { var output bytes.Buffer writer := NewSanitizer(&output, nil) _, _ = io.WriteString(writer, "before\x1b[31") if err := writer.Close(); err != nil { t.Fatal(err) } if output.String() != "before" { t.Fatalf("output=%q", output.String()) } if _, err := writer.Write([]byte("after")); !errors.Is(err, io.ErrClosedPipe) { t.Fatalf("write err=%v", err) } if err := writer.Close(); err != nil { t.Fatal(err) } } func TestBoundedSanitizerTruncatesOnceWithoutFailingWriter(t *testing.T) { var output bytes.Buffer writer := NewBoundedSanitizer(&output, [][]byte{[]byte("secret")}) input := append(bytes.Repeat([]byte("x"), MaxLogBytes), []byte("secret")...) if n, err := writer.Write(input); err != nil || n != len(input) { t.Fatalf("Write() = %d, %v", n, err) } if err := writer.Close(); err != nil { t.Fatal(err) } if !writer.Truncated() { t.Fatal("truncation not reported") } if output.Len() > MaxLogBytes || !strings.HasSuffix(output.String(), truncationMarker) || strings.Count(output.String(), truncationMarker) != 1 { t.Fatalf("output len=%d suffix=%q", output.Len(), output.String()[max(0, output.Len()-len(truncationMarker)):]) } if strings.Contains(output.String(), "secret") { t.Fatalf("secret leaked in output: %q", output.String()[max(0, output.Len()-128):]) } } func TestSanitizerSerializesConcurrentOutput(t *testing.T) { var output bytes.Buffer writer := NewSanitizer(&output, [][]byte{[]byte("secret")}) var wait sync.WaitGroup for range 20 { wait.Add(1) go func() { defer wait.Done() _, _ = io.WriteString(writer, "\x1b[32msecret\x1b[0m\n") }() } wait.Wait() if err := writer.Close(); err != nil { t.Fatal(err) } if strings.Count(output.String(), "[MASKED]\n") != 20 || strings.Contains(output.String(), "secret") || strings.ContainsRune(output.String(), '\x1b') { t.Fatalf("output=%q", output.String()) } } type rejectedWriter struct{} func (rejectedWriter) Write([]byte) (int, error) { return 0, errors.New("rejected") } func TestSanitizerReportsDestinationFailure(t *testing.T) { writer := NewSanitizer(rejectedWriter{}, nil) if _, err := writer.Write([]byte("x")); err == nil { t.Fatal("write failure hidden") } }