package services import ( "regexp" "strings" ) // 脱敏:日志里经常夹带凭据,送给外部模型前必须处理。 // // 原则: // - 宁可多脱一点,也不能漏掉凭据; // - 但**不要脱掉排查必需的信息**:内网 IP、端口、主机名、命名空间、 // Pod 名、错误码都要保留,否则模型给不出有用结论。 // - 每条规则都替换成带类型的占位符( / ), // 这样模型仍能看出"这里原本是个 token",语义不丢。 type redactRule struct { pattern *regexp.Regexp replacement string } var builtinRedactRules = []redactRule{ // Bearer / JWT —— 一定是凭据 {regexp.MustCompile(`(?i)bearer\s+[A-Za-z0-9\-._~+/]{12,}=*`), "Bearer "}, {regexp.MustCompile(`\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}`), ""}, // key=value 形式的敏感字段 {regexp.MustCompile(`(?i)\b(password|passwd|pwd|secret|token|api[_-]?key|access[_-]?key|secret[_-]?key)\b\s*[=:]\s*("?[^\s"',;]{4,}"?)`), "$1="}, // URL 里的 basic auth:scheme://user:pass@host // 密码里可能含 @(如 P@ssw0rd),所以取到**最后一个 @** 之前的所有内容: // 用贪婪 [^\s/]+ 再回溯到最后一个 @,能正确切出 password 与 host。 {regexp.MustCompile(`://([^:/\s]+):([^\s/]+)@`), "://$1:@"}, // AK/SK 形式(云厂商常见 16~40 位字母数字混合,带已知前缀) {regexp.MustCompile(`\b(AKIA|LTAI|AKID)[A-Za-z0-9]{12,}\b`), ""}, // 手机号 / 身份证 {regexp.MustCompile(`\b1[3-9]\d{9}\b`), ""}, {regexp.MustCompile(`\b\d{17}[\dXx]\b`), ""}, // 邮箱 {regexp.MustCompile(`\b[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}\b`), ""}, } // RedactText 对文本做脱敏,extra 是用户在设置里加的额外正则。 // // 额外正则写错(如括号不闭合)不能让整个分析失败:静默跳过, // 但上层会把这些失效规则一并提示给用户。 func RedactText(text string, extra []string) (string, []string) { out := text for _, rule := range builtinRedactRules { out = rule.pattern.ReplaceAllString(out, rule.replacement) } var invalid []string for _, raw := range extra { raw = strings.TrimSpace(raw) if raw == "" { continue } re, err := regexp.Compile(raw) if err != nil { invalid = append(invalid, raw) continue } out = re.ReplaceAllString(out, "") } return out, invalid }