package profile import ( "strings" "testing" ) func TestRedactTextConservativelyScansMalformedCredentials(t *testing.T) { tests := []struct { name string input string expected string }{ { name: "carriage return in password", input: "before http://alice:first\rsecond@proxy.example:8080 after", expected: "before http://alice:REDACTED@proxy.example:8080 after", }, { name: "line feed in password", input: "before http://alice:first\nsecond@proxy.example:8080 after", expected: "before http://alice:REDACTED@proxy.example:8080 after", }, { name: "spaces in password", input: "before http://alice:two words here@proxy.example:8080 after", expected: "before http://alice:REDACTED@proxy.example:8080 after", }, { name: "slash in password", input: "before http://alice:part/other@proxy.example:8080 after", expected: "before http://alice:REDACTED@proxy.example:8080 after", }, { name: "unsupported scheme and CRLF space slash password", input: "probe failed [ftp+ssh://alice:line one/line\r\ntwo@proxy.example:8080] retrying", expected: "probe failed [ftp+ssh://alice:REDACTED@proxy.example:8080] retrying", }, { name: "bare malformed userinfo", input: "before alice:bare secret/part@proxy.example:8080 after", expected: "before alice:REDACTED@proxy.example:8080 after", }, { name: "multiple at signs", input: "before http://alice:sec@ret@proxy.example:8080 after", expected: "before http://alice:REDACTED@proxy.example:8080 after", }, { name: "multiple URLs on one line", input: "left http://alice:first@one.example:1 middle invalid-scheme://bob:second value@two.example:2 right", expected: "left http://alice:REDACTED@one.example:1 middle invalid-scheme://bob:REDACTED@two.example:2 right", }, { name: "prose labels before URLs", input: "Proxy URL: odd+scheme://alice:first@one.example:1; fallback URL: invalid://bob:second@two.example:2", expected: "Proxy URL: odd+scheme://alice:REDACTED@one.example:1; fallback URL: invalid://bob:REDACTED@two.example:2", }, { name: "surrounding diagnostics with colons remain", input: "error: connect alice:secret@proxy.example:80: refused; code: EHOSTUNREACH", expected: "error: connect alice:REDACTED@proxy.example:80: refused; code: EHOSTUNREACH", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if got := RedactText(test.input); got != test.expected { t.Fatalf("RedactText() = %q, want %q", got, test.expected) } }) } } func TestRedactTextDoesNotSwallowUnrelatedTextAcrossLines(t *testing.T) { input := "diagnostic:\ncontact admin@example.com\nnext diagnostic remains" if got := RedactText(input); got != input { t.Fatalf("RedactText crossed a diagnostic line boundary: got %q, want %q", got, input) } input = "first http://alice:first@one.example:1\nsecond http://bob:second@two.example:2\ntrailing diagnostic" want := "first http://alice:REDACTED@one.example:1\nsecond http://bob:REDACTED@two.example:2\ntrailing diagnostic" if got := RedactText(input); got != want { t.Fatalf("RedactText swallowed across independent URL lines: got %q, want %q", got, want) } } func TestRedactUserinfoTextRemovesCompleteMalformedUserinfoAndPreservesContext(t *testing.T) { cases := []struct { input string userinfo string expected string }{ { input: "BEGIN unsupported://alice:secret words/part@proxy.example:80 END", userinfo: "alice:secret words/part", expected: "BEGIN unsupported://REDACTED@proxy.example:80 END", }, { input: "BEGIN alice:secret\r\nwords@proxy.example:80 END", userinfo: "alice:secret\r\nwords", expected: "BEGIN REDACTED@proxy.example:80 END", }, { input: "BEGIN http://alice:sec@ret@proxy.example:80 END", userinfo: "alice:sec@ret", expected: "BEGIN http://REDACTED@proxy.example:80 END", }, } for _, test := range cases { got := RedactUserinfoText(test.input) if strings.Contains(got, test.userinfo) { t.Fatalf("RedactUserinfoText retained userinfo %q in %q", test.userinfo, got) } if got != test.expected { t.Fatalf("RedactUserinfoText(%q) = %q, want %q", test.input, got, test.expected) } if !strings.HasPrefix(got, "BEGIN ") || !strings.HasSuffix(got, " END") { t.Fatalf("RedactUserinfoText swallowed surrounding diagnostics: %q", got) } } } func TestRedactionPreservesArbitrarySurroundingDiagnostics(t *testing.T) { prefixes := []string{"", "error: ", "[target apt] ", "first URL failed; "} suffixes := []string{"", " (retrying)", "; next check continues", "\nstack trace follows"} for _, prefix := range prefixes { for _, suffix := range suffixes { input := prefix + "odd+scheme://user:malformed secret/path@host.example:9" + suffix got := RedactText(input) if strings.Contains(got, "malformed secret/path") { t.Fatalf("secret survived for prefix=%q suffix=%q: %q", prefix, suffix, got) } if !strings.HasPrefix(got, prefix) || !strings.HasSuffix(got, suffix) { t.Fatalf("surrounding diagnostics changed for prefix=%q suffix=%q: %q", prefix, suffix, got) } } } }