// Package guardrail 提供 Agent CLI 输入输出治理能力。 // Logan.Li package guardrail import ( "crypto/rand" "encoding/hex" "encoding/json" "regexp" "strings" ) var sensitivePatterns = []*regexp.Regexp{ regexp.MustCompile(`(?i)(bearer\s+)[a-z0-9._~+/=-]{16,}`), regexp.MustCompile(`(?i)((?:password|passwd|pwd|secret|token|api[_-]?key|access[_-]?key|secret[_-]?key)\s*[:=]\s*)[^\s'"&]+`), regexp.MustCompile(`(?i)((?:mysql|postgres|postgresql|redis|mongodb)://[^:\s/@]+:)[^@\s]+(@)`), regexp.MustCompile(`(?is)-----BEGIN [A-Z ]*PRIVATE KEY-----.*?-----END [A-Z ]*PRIVATE KEY-----`), regexp.MustCompile(`(?i)((?:Cookie|Set-Cookie):\s*)[^\r\n]+`), regexp.MustCompile(`(?i)AKIA[0-9A-Z]{16}`), } var sensitiveKeys = map[string]bool{ "password": true, "passwd": true, "pwd": true, "secret": true, "token": true, "api_key": true, "apikey": true, "access_key": true, "secret_key": true, "private_key": true, "cookie": true, } // SanitizeText 脱敏工具输出、LLM 文本和错误信息,避免敏感信息进入前端/DB/记忆。 // Logan.Li func SanitizeText(input string) string { if input == "" { return "" } out := input for _, re := range sensitivePatterns { out = re.ReplaceAllStringFunc(out, func(match string) string { if strings.Contains(match, "PRIVATE KEY") { return "[REDACTED_PRIVATE_KEY]" } sub := re.FindStringSubmatch(match) if len(sub) >= 3 { return sub[1] + "[REDACTED]" + sub[2] } if len(sub) >= 2 { return sub[1] + "[REDACTED]" } return "[REDACTED]" }) } return out } // SanitizeJSON 脱敏 JSON 文本;非法 JSON 时退回文本脱敏。 // Logan.Li func SanitizeJSON(raw string) string { if strings.TrimSpace(raw) == "" { return raw } var v interface{} if err := json.Unmarshal([]byte(raw), &v); err != nil { return SanitizeText(raw) } v = sanitizeValue(v) b, err := json.Marshal(v) if err != nil { return SanitizeText(raw) } return string(b) } // SanitizeMap 返回脱敏后的参数副本,避免事件、审计和记忆泄露工具参数中的凭证。 // Logan.Li func SanitizeMap(input map[string]interface{}) map[string]interface{} { if input == nil { return nil } sanitized, ok := sanitizeValue(input).(map[string]interface{}) if !ok { return map[string]interface{}{} } return sanitized } func sanitizeValue(v interface{}) interface{} { switch val := v.(type) { case map[string]interface{}: out := make(map[string]interface{}, len(val)) for k, item := range val { if sensitiveKeys[strings.ToLower(strings.ReplaceAll(k, "-", "_"))] { out[k] = "[REDACTED]" continue } out[k] = sanitizeValue(item) } return out case []interface{}: for i := range val { val[i] = sanitizeValue(val[i]) } return val case string: return SanitizeText(val) default: return val } } // NewTraceID 生成短 trace id,便于串联一次 Agent 任务。 // Logan.Li func NewTraceID() string { return "tr-" + randomHex(12) } // NewSpanID 生成短 span id,便于串联工具和消息事件。 // Logan.Li func NewSpanID() string { return "sp-" + randomHex(8) } func randomHex(n int) string { buf := make([]byte, n) if _, err := rand.Read(buf); err != nil { return "0000000000000000" } return hex.EncodeToString(buf) }