diff --git a/pkg/microservice/aslan/core/system/service/proxy.go b/pkg/microservice/aslan/core/system/service/proxy.go index 01c06c7813..3e85f79bdd 100644 --- a/pkg/microservice/aslan/core/system/service/proxy.go +++ b/pkg/microservice/aslan/core/system/service/proxy.go @@ -55,6 +55,10 @@ func ListProxies(log *zap.SugaredLogger) ([]*commonmodels.Proxy, error) { log.Errorf("Proxy.List error: %v", err) return resp, e.ErrListProxies.AddErr(err) } + for _, proxy := range resp { + proxy.Username = "" + proxy.Password = "" + } return resp, nil } diff --git a/pkg/tool/errors/errors.go b/pkg/tool/errors/errors.go index 9f3bd0c9a8..77f1a91435 100644 --- a/pkg/tool/errors/errors.go +++ b/pkg/tool/errors/errors.go @@ -137,7 +137,7 @@ func ErrorMessage(err error) (code int, message map[string]interface{}) { "type": "error", "message": v.Message(), "code": v.Code(), - "description": v.Desc(), + "description": sanitizeSensitiveInfo(v.Desc()), "extra": v.Extra(), } } @@ -146,6 +146,6 @@ func ErrorMessage(err error) (code int, message map[string]interface{}) { return internalErr.Code(), map[string]interface{}{ "message": internalErr.Error(), "code": internalErr.Code(), - "description": err.Error(), + "description": sanitizeSensitiveInfo(err.Error()), } } diff --git a/pkg/tool/errors/sanitize.go b/pkg/tool/errors/sanitize.go new file mode 100644 index 0000000000..2bfca1d2a0 --- /dev/null +++ b/pkg/tool/errors/sanitize.go @@ -0,0 +1,106 @@ +package errors + +import ( + "net/url" + "regexp" + "strings" +) + +var ( + urlCandidatePattern = regexp.MustCompile(`https?://[^\s",<>()\[\]{}]+`) + authorizationHeaderPattern = regexp.MustCompile(`(?i)(authorization[:=]\s*(?:basic|bearer)\s+)[^,\s"]+`) + sensitiveQueryKeys = map[string]struct{}{ + "username": {}, + "user_name": {}, + "password": {}, + "passwd": {}, + "pwd": {}, + "token": {}, + "access_token": {}, + "refresh_token": {}, + "access_key": {}, + "access_key_id": {}, + "access_key_secret": {}, + "secret": {}, + "client_secret": {}, + "private_access_token": {}, + } +) + +func sanitizeSensitiveInfo(text string) string { + if text == "" { + return text + } + + text = sanitizeURLsInText(text) + text = authorizationHeaderPattern.ReplaceAllString(text, `${1}***`) + + return text +} + +func sanitizeURLsInText(text string) string { + return urlCandidatePattern.ReplaceAllStringFunc(text, sanitizeURLString) +} + +func sanitizeURLString(raw string) string { + raw = sanitizeURLUserInfo(raw) + raw = sanitizeURLQuery(raw) + return raw +} + +func sanitizeURLUserInfo(raw string) string { + parsed, err := url.Parse(raw) + if err != nil || parsed.User == nil { + return raw + } + + schemeIdx := strings.Index(raw, "://") + if schemeIdx < 0 { + return raw + } + + userInfoStart := schemeIdx + 3 + atOffset := strings.Index(raw[userInfoStart:], "@") + if atOffset < 0 { + return raw + } + + replacement := "***" + if _, hasPassword := parsed.User.Password(); hasPassword { + replacement = "***:***" + } + + userInfoEnd := userInfoStart + atOffset + return raw[:userInfoStart] + replacement + raw[userInfoEnd:] +} + +func sanitizeURLQuery(raw string) string { + queryStart := strings.Index(raw, "?") + if queryStart < 0 { + return raw + } + + queryEnd := len(raw) + if fragmentStart := strings.Index(raw[queryStart+1:], "#"); fragmentStart >= 0 { + queryEnd = queryStart + 1 + fragmentStart + } + + queryParts := strings.Split(raw[queryStart+1:queryEnd], "&") + for i, part := range queryParts { + key, _, found := strings.Cut(part, "=") + decodedKey, err := url.QueryUnescape(key) + if err != nil { + decodedKey = key + } + if _, ok := sensitiveQueryKeys[strings.ToLower(decodedKey)]; !ok { + continue + } + if found { + queryParts[i] = key + "=***" + } else { + queryParts[i] = key + } + } + + return raw[:queryStart+1] + strings.Join(queryParts, "&") + raw[queryEnd:] +}