74 lines
2.9 KiB
Go
74 lines
2.9 KiB
Go
package plugins
|
|
|
|
import (
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// maxPluginErrorBytes bounds the diagnostic persisted into a plugin record.
|
|
// Failure messages can originate in subprocess/transport errors, so keeping
|
|
// them bounded prevents an unexpectedly verbose error from bloating the
|
|
// record or the settings API response.
|
|
const maxPluginErrorBytes = 2048
|
|
|
|
var (
|
|
pluginBearerTokenPattern = regexp.MustCompile(`(?i)\bBearer\s+[A-Za-z0-9._~+/=-]+`)
|
|
pluginLabeledSecretPattern = regexp.MustCompile(`(?i)\b((?:[A-Za-z0-9]+[_-])*(?:password|passwd|passphrase|secret|token|api[_ -]?(?:key|token|secret)|access[_ -]?token|refresh[_ -]?token|pat))\b(\s*[:=]\s*)(?:"[^"]*"|'[^']*'|[^\s,;}]+)`)
|
|
pluginPATPattern = regexp.MustCompile(`\b(?:github_pat_[A-Za-z0-9_]+|gh[pousr]_[A-Za-z0-9]+|glpat-[A-Za-z0-9_-]+)\b`)
|
|
pluginHomePathPattern = regexp.MustCompile(`(/Users/[^/\s]+|/home/[^/\s]+|/root)(/|$)`)
|
|
pluginWindowsHomePattern = regexp.MustCompile(`(?i)([A-Za-z]:[/\\]Users[/\\][^/\\\s]+)([/\\]|$)`)
|
|
pluginURLCredentialPattern = regexp.MustCompile(`(?i)(https?|ftp)://[^:@/\s]+:[^@/\s]+@`)
|
|
|
|
pluginHomePatternOnce sync.Once
|
|
cachedPluginHomePattern *regexp.Regexp
|
|
)
|
|
|
|
// normalizePluginError turns a runtime failure into a compact, single-line
|
|
// diagnostic suitable for persistence and display. Runtime errors can include
|
|
// arbitrary go-plugin subprocess output, so credential-like values and home
|
|
// paths are removed before the bounded message is stored.
|
|
func normalizePluginError(err error) string {
|
|
if err == nil {
|
|
return ""
|
|
}
|
|
message := strings.Join(strings.Fields(err.Error()), " ")
|
|
message = strings.ToValidUTF8(message, "�")
|
|
message = redactPluginError(message)
|
|
if len([]byte(message)) <= maxPluginErrorBytes {
|
|
return message
|
|
}
|
|
|
|
return truncatePluginError(message)
|
|
}
|
|
|
|
func truncatePluginError(message string) string {
|
|
const marker = "…"
|
|
content := []byte(message)
|
|
cut := maxPluginErrorBytes - len([]byte(marker))
|
|
for cut > 0 && cut < len(content) && !utf8.RuneStart(content[cut]) {
|
|
cut--
|
|
}
|
|
return string(content[:cut]) + marker
|
|
}
|
|
|
|
func redactPluginError(message string) string {
|
|
pluginHomePatternOnce.Do(func() {
|
|
if home, err := os.UserHomeDir(); err == nil && home != "" && home != "/" {
|
|
cachedPluginHomePattern = regexp.MustCompile(regexp.QuoteMeta(home) + `([/\\]|$)`)
|
|
}
|
|
})
|
|
if cachedPluginHomePattern != nil {
|
|
message = cachedPluginHomePattern.ReplaceAllString(message, "~$1")
|
|
}
|
|
message = pluginHomePathPattern.ReplaceAllString(message, "~$2")
|
|
message = pluginWindowsHomePattern.ReplaceAllString(message, "~$2")
|
|
message = pluginURLCredentialPattern.ReplaceAllString(message, "${1}://[REDACTED]@")
|
|
message = pluginBearerTokenPattern.ReplaceAllString(message, "Bearer [REDACTED]")
|
|
message = pluginLabeledSecretPattern.ReplaceAllString(message, "${1}${2}[REDACTED]")
|
|
message = pluginPATPattern.ReplaceAllString(message, "[REDACTED]")
|
|
return message
|
|
}
|