79 lines
1.9 KiB
Go
79 lines
1.9 KiB
Go
package playground
|
|
|
|
import (
|
|
"encoding/json"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// Redacted replaces every value Redact removes.
|
|
const Redacted = "[redacted]"
|
|
|
|
// piiKeys: a field whose name contains one of these is personal or free text
|
|
// (free text is where people type phone numbers and addresses).
|
|
var piiKeys = []string{
|
|
"name", "phone", "mobile", "email", "address", "landmark", "otp", "contact",
|
|
"note", "remark", "instruction", "description", "reason", "comment",
|
|
}
|
|
|
|
var (
|
|
emailLike = regexp.MustCompile(`[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}`)
|
|
// An Indian mobile (10 digits from 6-9, optional +91). Narrow on purpose:
|
|
// a looser digit-run pattern also masks dates like "2026-09-29 12".
|
|
phoneLike = regexp.MustCompile(`(?:\+?91[\s-]?)?\b[6-9]\d{4}[\s-]?\d{5}\b`)
|
|
)
|
|
|
|
// Redact returns a copy of v, as plain JSON values, with personal data
|
|
// removed: values under personal keys are replaced, and any remaining string
|
|
// that contains an email or a phone-like number has it masked.
|
|
//
|
|
// Executors already select only non-personal columns; this is the backstop
|
|
// that makes a later column addition safe by default.
|
|
func Redact(v any) any {
|
|
b, err := json.Marshal(v)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
var generic any
|
|
if err := json.Unmarshal(b, &generic); err != nil {
|
|
return nil
|
|
}
|
|
return redactValue(generic)
|
|
}
|
|
|
|
func redactValue(v any) any {
|
|
switch t := v.(type) {
|
|
case map[string]any:
|
|
out := make(map[string]any, len(t))
|
|
for k, val := range t {
|
|
if isPIIKey(k) && val != nil {
|
|
out[k] = Redacted
|
|
continue
|
|
}
|
|
out[k] = redactValue(val)
|
|
}
|
|
return out
|
|
case []any:
|
|
out := make([]any, len(t))
|
|
for i, val := range t {
|
|
out[i] = redactValue(val)
|
|
}
|
|
return out
|
|
case string:
|
|
s := emailLike.ReplaceAllString(t, Redacted)
|
|
return phoneLike.ReplaceAllString(s, Redacted)
|
|
default:
|
|
return v
|
|
}
|
|
}
|
|
|
|
func isPIIKey(k string) bool {
|
|
k = strings.ToLower(k)
|
|
for _, p := range piiKeys {
|
|
if strings.Contains(k, p) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|