109 lines
2.9 KiB
Go
109 lines
2.9 KiB
Go
// 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)
|
|
}
|
|
|
|
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)
|
|
}
|