Adds internal/sanitize: rewrites the customer-identifying spans that internal/privacy detects (domain/FQDN/e-mail/AD/public-IP/timezone) with same-length neutral fillers, in place, without changing the file format. - Fillers keep byte length: "sigma.sbrf.ru" -> "xxxxx.xxxx.xx", IP -> "00.000.000.00", "Europe/Moscow" -> "Etc/Universal" (same-length valid neutral IANA zone), offset "180" -> "000". Timestamps are not recomputed. - Lossless recursive archive walk (tar/.sds/gz/tgz/zip): entry names, modes, and all embedded timestamps preserved; untouched zip entries copied raw; member payload length unchanged so tar headers stay byte-identical; only the .gz/.zip compression layer is rebuilt. 0 redactions -> byte-identical output. - privacy.FindSpans is the one matcher shared by detection and redaction; fillers are recognised by isRedactionFiller so a re-scan / second pass is a no-op. New privacy FPs fixed along the way: syslog selectors (local7.info), "MEVersion" firmware quads, *.conf_bak vendor templates, bundled viewer domains. - Binary members (FRU.bin, localtime, redis-dump.rdb, SOL captures) and unreadable nested archives are reported in Result.SkippedBinary, never edited. - Surfaces: POST /api/sanitize (+ GET /api/sanitize/download), the "Обезличить и скачать копию" button in the Customer-data panel, and logpile -sanitize <file> (restores mtime/atime). Verified: re-parsing a sanitized Dell TSR / xFusion / Inspur onekeylog / H3C .sds yields the identical hardware inventory; re-scan is clean. ADL-067, bible-local/docs/log-sanitization.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
63 lines
1.9 KiB
Go
63 lines
1.9 KiB
Go
package privacy
|
|
|
|
import "net"
|
|
|
|
// Documentation, benchmarking, and well-known example addresses that carry no
|
|
// site information even though they are globally routable.
|
|
var nonSensitiveNets = func() []*net.IPNet {
|
|
cidrs := []string{
|
|
"192.0.2.0/24", // RFC 5737 TEST-NET-1
|
|
"198.51.100.0/24", // RFC 5737 TEST-NET-2
|
|
"203.0.113.0/24", // RFC 5737 TEST-NET-3
|
|
"198.18.0.0/15", // RFC 2544 benchmarking
|
|
"100.64.0.0/10", // RFC 6598 CGNAT
|
|
"192.88.99.0/24", // RFC 7526 6to4 relay anycast
|
|
"2001:db8::/32", // RFC 3849 documentation
|
|
}
|
|
out := make([]*net.IPNet, 0, len(cidrs))
|
|
for _, c := range cidrs {
|
|
if _, n, err := net.ParseCIDR(c); err == nil {
|
|
out = append(out, n)
|
|
}
|
|
}
|
|
return out
|
|
}()
|
|
|
|
var nonSensitiveExact = map[string]struct{}{
|
|
"8.8.8.8": {}, "8.8.4.4": {}, "1.1.1.1": {}, "1.0.0.1": {},
|
|
"4.2.2.2": {}, "4.2.2.1": {}, "9.9.9.9": {}, "1.2.3.4": {}, "2.4.6.8": {},
|
|
"208.67.222.222": {}, "208.67.220.220": {},
|
|
"77.88.8.8": {}, "77.88.8.1": {}, "77.88.8.88": {}, // Yandex public DNS
|
|
"100.2.74.41": {}, // Kaytus/Inspur upnp/config.json factory default
|
|
}
|
|
|
|
// isSensitiveIP reports whether s is a routable address that could identify the
|
|
// customer's provider or site. Private (RFC1918/ULA), loopback, link-local,
|
|
// multicast, and the example/benchmark ranges above are not sensitive.
|
|
func isSensitiveIP(s string) bool {
|
|
ip := net.ParseIP(s)
|
|
if ip == nil {
|
|
return false
|
|
}
|
|
if _, ok := nonSensitiveExact[s]; ok {
|
|
return false
|
|
}
|
|
if ip.IsLoopback() || ip.IsPrivate() || ip.IsUnspecified() ||
|
|
ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() ||
|
|
ip.IsMulticast() || ip.IsInterfaceLocalMulticast() {
|
|
return false
|
|
}
|
|
if !ip.IsGlobalUnicast() {
|
|
return false
|
|
}
|
|
if v4 := ip.To4(); v4 != nil && (v4[0] == 0 || v4[0] == 255 || v4[0] >= 240) {
|
|
return false
|
|
}
|
|
for _, n := range nonSensitiveNets {
|
|
if n.Contains(ip) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|