Files
gnoma/internal/security/redactor.go
vikingowl 33dec722b8 feat: add security firewall with secret scanning and incognito mode
internal/security/ — core security layer baked into gnoma:
- Secret scanner: gitleaks-derived regex patterns (Anthropic, OpenAI,
  AWS, GitHub, GitLab, Slack, Stripe, private keys, DB URLs, generic
  secrets) + Shannon entropy detection for unknown formats
- Redactor: replaces matched secrets with [REDACTED], merges
  overlapping ranges, preserves surrounding context
- Unicode sanitizer: NFKC normalization, strips Cf/Co categories,
  tag characters (ASCII smuggling), zero-width chars, RTL overrides
- Incognito mode: suppresses persistence, learning, content logging
- Firewall: wraps engine, scans outgoing messages + system prompt +
  tool results before they reach the provider

Wired into engine and CLI. 21 security tests.
2026-04-03 14:07:50 +02:00

52 lines
1.3 KiB
Go

package security
import "sort"
const redactedPlaceholder = "[REDACTED]"
// Redact replaces detected secrets in content with [REDACTED].
// Preserves surrounding context (quotes, delimiters).
func Redact(content string, matches []SecretMatch) string {
if len(matches) == 0 {
return content
}
// Filter to redact-only and sort by start position ascending
var redacts []SecretMatch
for _, m := range matches {
if m.Action == ActionRedact && m.Start >= 0 && m.End <= len(content) && m.Start < m.End {
redacts = append(redacts, m)
}
}
if len(redacts) == 0 {
return content
}
sort.Slice(redacts, func(i, j int) bool {
return redacts[i].Start < redacts[j].Start
})
// Merge overlapping ranges
merged := []SecretMatch{redacts[0]}
for _, m := range redacts[1:] {
last := &merged[len(merged)-1]
if m.Start <= last.End {
// Overlapping — extend the range
if m.End > last.End {
last.End = m.End
}
} else {
merged = append(merged, m)
}
}
// Build result replacing merged ranges from end to start
result := []byte(content)
for i := len(merged) - 1; i >= 0; i-- {
m := merged[i]
replacement := []byte(redactedPlaceholder)
result = append(result[:m.Start], append(replacement, result[m.End:]...)...)
}
return string(result)
}