137 lines
3.6 KiB
Go
137 lines
3.6 KiB
Go
package redaction
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
type RedactionResult struct {
|
|
Text string
|
|
Applied bool
|
|
Findings []Finding
|
|
}
|
|
|
|
type Finding struct {
|
|
Type string
|
|
Count int
|
|
}
|
|
|
|
type Redactor interface {
|
|
RedactText(input string) RedactionResult
|
|
RedactJSON(input map[string]any) map[string]any
|
|
RedactBytesForLog(input []byte) string
|
|
}
|
|
|
|
type DefaultRedactor struct{}
|
|
|
|
func New() DefaultRedactor { return DefaultRedactor{} }
|
|
|
|
func (DefaultRedactor) RedactText(input string) RedactionResult {
|
|
out := input
|
|
counts := map[string]int{}
|
|
apply := func(kind string, fn func(string) string) {
|
|
before := out
|
|
out = fn(out)
|
|
if out != before {
|
|
counts[kind]++
|
|
}
|
|
}
|
|
apply("database_url", func(s string) string { return databaseURLPattern.ReplaceAllString(s, `${1}:***MASKED***@`) })
|
|
apply("api_key", func(s string) string { return openAIKeyPattern.ReplaceAllString(s, `sk-***MASKED***`) })
|
|
apply("bearer", func(s string) string { return bearerPattern.ReplaceAllString(s, `Bearer ***MASKED***`) })
|
|
apply("secret", func(s string) string { return secretKVPattern.ReplaceAllString(s, `${1}=***MASKED***`) })
|
|
apply("email", func(s string) string { return emailPattern.ReplaceAllString(s, `${1}***${3}`) })
|
|
apply("otp", func(s string) string {
|
|
return otpPattern.ReplaceAllStringFunc(s, func(m string) string {
|
|
if strings.Contains(strings.ToLower(m), "услуг") {
|
|
return m
|
|
}
|
|
parts := otpPattern.FindStringSubmatch(m)
|
|
if len(parts) != 3 {
|
|
return m
|
|
}
|
|
return parts[1] + "CODE"
|
|
})
|
|
})
|
|
apply("account", func(s string) string {
|
|
return accountPattern.ReplaceAllStringFunc(s, func(m string) string {
|
|
parts := accountPattern.FindStringSubmatch(m)
|
|
if len(parts) != 3 {
|
|
return m
|
|
}
|
|
return parts[1] + maskMiddle(parts[2], 0, 4, "****")
|
|
})
|
|
})
|
|
apply("phone", func(s string) string {
|
|
return phonePattern.ReplaceAllStringFunc(s, func(m string) string { return maskMiddle(m, 4, 4, "***") })
|
|
})
|
|
apply("iin", func(s string) string {
|
|
return digits12Pattern.ReplaceAllStringFunc(s, func(m string) string { return maskMiddle(m, 4, 4, "****") })
|
|
})
|
|
apply("card", func(s string) string {
|
|
return digits16Pattern.ReplaceAllStringFunc(s, func(m string) string { return maskMiddle(m, 4, 4, "********") })
|
|
})
|
|
findings := make([]Finding, 0, len(counts))
|
|
for k, v := range counts {
|
|
findings = append(findings, Finding{Type: k, Count: v})
|
|
}
|
|
return RedactionResult{Text: out, Applied: out != input, Findings: findings}
|
|
}
|
|
|
|
func (r DefaultRedactor) RedactJSON(input map[string]any) map[string]any {
|
|
return redactMap(r, input)
|
|
}
|
|
|
|
func (DefaultRedactor) RedactBytesForLog(input []byte) string {
|
|
return fmt.Sprintf("[bytes:%d redacted]", len(input))
|
|
}
|
|
|
|
func redactMap(r DefaultRedactor, input map[string]any) map[string]any {
|
|
out := map[string]any{}
|
|
for k, v := range input {
|
|
out[k] = redactValue(r, v)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func redactValue(r DefaultRedactor, v any) any {
|
|
switch x := v.(type) {
|
|
case string:
|
|
return r.RedactText(x).Text
|
|
case map[string]any:
|
|
return redactMap(r, x)
|
|
case []any:
|
|
out := make([]any, len(x))
|
|
for i := range x {
|
|
out[i] = redactValue(r, x[i])
|
|
}
|
|
return out
|
|
case []string:
|
|
out := make([]string, len(x))
|
|
for i := range x {
|
|
out[i] = r.RedactText(x[i]).Text
|
|
}
|
|
return out
|
|
default:
|
|
return v
|
|
}
|
|
}
|
|
|
|
func maskMiddle(value string, keepStart, keepEnd int, mask string) string {
|
|
if !utf8.ValidString(value) {
|
|
return "***MASKED***"
|
|
}
|
|
r := []rune(value)
|
|
if keepStart == 0 && keepEnd > 0 {
|
|
if len(r) <= keepEnd {
|
|
return mask
|
|
}
|
|
return mask + string(r[len(r)-keepEnd:])
|
|
}
|
|
if len(r) <= keepStart+keepEnd {
|
|
return strings.Repeat("*", len(r))
|
|
}
|
|
return string(r[:keepStart]) + mask + string(r[len(r)-keepEnd:])
|
|
}
|