feat(privacy): scan ingested sources for customer-identifying data
Detection-only scan (internal/privacy) attached to every AnalysisResult: a customer-domain guess plus a findings list (category, file, line, match, hint), ported from the KB grep playbook. Runs on archive uploads and the serialized Redfish tree; gated by LOGPILE_PRIVACY_SCAN (default on). Surfaced at GET /api/privacy-scan, in the "Customer data" UI panel, and as privacy_report.json in the raw-export bundle. IP policy keeps RFC1918 and example ranges out of findings; allowlist covers standards-body and vendor infrastructure domains. No customer tokens in the repo. See ADL-066 and bible-local/docs/privacy-scan.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
3311bafd8e
commit
4a4910f207
@@ -0,0 +1,81 @@
|
||||
package privacy
|
||||
|
||||
import (
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Public infrastructure, documentation, and vendor-default values that are not
|
||||
// customer leaks. These are reference data (RFC 2606 / 5737 names, well-known
|
||||
// NTP pools, standards-body domains, factory defaults), not vendor-detection
|
||||
// logic.
|
||||
var (
|
||||
allowlistedZones = []string{
|
||||
"example.com", "example.net", "example.org", "example.local", "example.edu",
|
||||
"foobar.edu", "issue.net",
|
||||
"localhost", "localdomain", "local.lan",
|
||||
"pool.ntp.org", "ntp.org", "nist.gov", "windows.com", "microsoft.com",
|
||||
"dmtf.org", "iana.org", "openssl.org", "openssh.com", "openssh.org",
|
||||
"libssh.org", "rsyslog.com", "adiscon.com", "redhat.com", "kernel.org",
|
||||
"megarac.com", "ami.com", "commond.com",
|
||||
"oasis-open.org", "w3.org", "xmlsoap.org", "purl.org", "ietf.org",
|
||||
"inspur.com", "inspurcloud.com", "inservice-iq.com", "kaytus.com",
|
||||
"jd.com", "jd.local", "jdcloud.com", "in-addr.arpa", "ip6.arpa", "arpa",
|
||||
}
|
||||
|
||||
allowlistedValues = map[string]struct{}{
|
||||
"asia/shanghai": {},
|
||||
"etc/utc": {},
|
||||
"utc": {},
|
||||
"to be filled by o.e.m.": {},
|
||||
"default string": {},
|
||||
"unknown": {},
|
||||
"n/a": {},
|
||||
"none": {},
|
||||
"null": {},
|
||||
"0": {},
|
||||
"0.0.0.0": {},
|
||||
}
|
||||
|
||||
// Archive members that are vendor factory templates, not the active config.
|
||||
allowlistedFilenameParts = []string{
|
||||
"_tencent", "_jingdong", "_pdd", "_baidu", "_kuaishou", "_tianyiyun",
|
||||
"_jd.", "syslog_jd", "snmptrapcfg", ".json_bak", "ntp_auto",
|
||||
}
|
||||
)
|
||||
|
||||
func isAllowlistedDomain(domain string) bool {
|
||||
d := strings.ToLower(strings.TrimSuffix(strings.TrimSpace(domain), "."))
|
||||
for _, z := range allowlistedZones {
|
||||
if d == z || strings.HasSuffix(d, "."+z) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isAllowlistedValue(v string) bool {
|
||||
lv := strings.ToLower(strings.TrimSpace(v))
|
||||
if _, ok := allowlistedValues[lv]; ok {
|
||||
return true
|
||||
}
|
||||
return isAllowlistedDomain(v)
|
||||
}
|
||||
|
||||
func isAllowlistedFilename(p string) bool {
|
||||
lp := strings.ToLower(p)
|
||||
for _, part := range allowlistedFilenameParts {
|
||||
if strings.Contains(lp, part) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isCertFilename(p string) bool {
|
||||
switch strings.ToLower(path.Ext(p)) {
|
||||
case ".pem", ".csr", ".crt", ".cer":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user