update
This commit is contained in:
1 parent
0690285147
commit
5618cd2738
154 files changed
+5021
-3161
No files matched your search
@@ -0,0 +1,108 @@
|
||||
// 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)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package guardrail
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSanitizeTextMasksCommonSecrets(t *testing.T) {
|
||||
input := "Authorization: Bearer abcdefghijklmnopqrstuvwxyz password=secret123 mysql://root:pass@db/prod"
|
||||
got := SanitizeText(input)
|
||||
for _, leak := range []string{"abcdefghijklmnopqrstuvwxyz", "secret123", "root:pass@db"} {
|
||||
if strings.Contains(got, leak) {
|
||||
t.Fatalf("敏感信息未脱敏: %s in %s", leak, got)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(got, "[REDACTED]") {
|
||||
t.Fatalf("脱敏标记缺失: %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizeJSONMasksSensitiveKeys(t *testing.T) {
|
||||
got := SanitizeJSON(`{"command":"echo ok","password":"p1","nested":{"api_key":"k1"}}`)
|
||||
for _, leak := range []string{"p1", "k1"} {
|
||||
if strings.Contains(got, leak) {
|
||||
t.Fatalf("JSON 敏感字段未脱敏: %s in %s", leak, got)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(got, `"command":"echo ok"`) {
|
||||
t.Fatalf("非敏感字段不应被改写: %s", got)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user