pi-agent: Implement final TUI hardening

This commit is contained in:
proxyctl coordinator
2026-07-18 01:07:54 +08:00
parent 954e7d977a
commit f15ffc2301
10 changed files with 598 additions and 39 deletions
+18 -6
View File
@@ -263,19 +263,26 @@ func normalizeBypassEntry(raw string) (string, error) {
return host, nil
}
var credentialPattern = regexp.MustCompile(`(?i)((?:https?|socks5h?)://)?([^/@\s:]+):([^@\s]*)@`)
var (
schemeCredentialPattern = regexp.MustCompile(`(?i)((?:https?|socks5h?)://)([^/@:\r\n]+):([^@\r\n]*)@`)
bareCredentialPattern = regexp.MustCompile(`(?i)([^/@\s:]+):([^@/\s]*)@`)
)
// RedactText removes URL passwords from diagnostic text without requiring the
// surrounding text to be a valid URL or to include a scheme.
// surrounding text to be a valid URL. The scheme-aware first pass deliberately
// accepts spaces and other malformed password bytes up to @; diagnostics and
// form validation must not expose a credential merely because parsing failed.
func RedactText(value string) string {
return credentialPattern.ReplaceAllString(value, `${1}${2}:REDACTED@`)
value = schemeCredentialPattern.ReplaceAllString(value, `${1}${2}:REDACTED@`)
return bareCredentialPattern.ReplaceAllString(value, `${1}:REDACTED@`)
}
// RedactUserinfoText removes usernames as well as passwords. Form frames use
// this stricter boundary because even the identity of the probe source is
// private while a Profile is being edited.
func RedactUserinfoText(value string) string {
return credentialPattern.ReplaceAllString(value, `${1}REDACTED@`)
value = schemeCredentialPattern.ReplaceAllString(value, `${1}REDACTED@`)
return bareCredentialPattern.ReplaceAllString(value, `REDACTED@`)
}
func ObscureURL(value string) string {
@@ -294,14 +301,19 @@ func ObscureURL(value string) string {
// rune count. A text input clone can therefore retain its real cursor and
// horizontal viewport positions without rendering either username or password.
func ObscureURLForEditing(value string) string {
matches := credentialPattern.FindAllStringSubmatchIndex(value, -1)
masked := maskCredentialMatches(value, schemeCredentialPattern, 4, 7)
return maskCredentialMatches(masked, bareCredentialPattern, 2, 5)
}
func maskCredentialMatches(value string, pattern *regexp.Regexp, userinfoStartGroup, userinfoEndGroup int) string {
matches := pattern.FindAllStringSubmatchIndex(value, -1)
if len(matches) == 0 {
return value
}
var masked strings.Builder
last := 0
for _, match := range matches {
userinfoStart, userinfoEnd := match[4], match[7]
userinfoStart, userinfoEnd := match[userinfoStartGroup], match[userinfoEndGroup]
masked.WriteString(value[last:userinfoStart])
masked.WriteString(strings.Repeat("•", len([]rune(value[userinfoStart:userinfoEnd]))))
last = userinfoEnd