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,85 @@
|
||||
package privacy
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.mchus.pro/mchus/logpile/internal/models"
|
||||
)
|
||||
|
||||
func TestIsSensitiveIP(t *testing.T) {
|
||||
cases := []struct {
|
||||
ip string
|
||||
want bool
|
||||
}{
|
||||
{"45.32.10.7", true},
|
||||
{"93.184.216.34", true},
|
||||
{"2606:2800:220:1:248:1893:25c8:1946", true},
|
||||
{"10.1.2.3", false},
|
||||
{"172.16.5.5", false},
|
||||
{"192.168.31.4", false},
|
||||
{"127.0.0.1", false},
|
||||
{"169.254.1.1", false},
|
||||
{"0.0.0.0", false},
|
||||
{"255.255.255.255", false},
|
||||
{"224.0.0.1", false},
|
||||
{"203.0.113.9", false},
|
||||
{"198.51.100.1", false},
|
||||
{"192.0.2.7", false},
|
||||
{"8.8.8.8", false},
|
||||
{"1.1.1.1", false},
|
||||
{"100.64.0.1", false},
|
||||
{"not-an-ip", false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := isSensitiveIP(c.ip); got != c.want {
|
||||
t.Errorf("isSensitiveIP(%q) = %v, want %v", c.ip, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAllowlistedDomain(t *testing.T) {
|
||||
yes := []string{"example.com", "host.example.local", "pool.ntp.org", "0.pool.ntp.org", "redhat.com", "a.b.jd.com"}
|
||||
no := []string{"corp.acme.ru", "tcs.example-bank.com", "sigma.internal.io"}
|
||||
for _, d := range yes {
|
||||
if !isAllowlistedDomain(d) {
|
||||
t.Errorf("%q should be allowlisted", d)
|
||||
}
|
||||
}
|
||||
for _, d := range no {
|
||||
if isAllowlistedDomain(d) {
|
||||
t.Errorf("%q should not be allowlisted", d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistrableDomain(t *testing.T) {
|
||||
cases := map[string]string{
|
||||
"sn-x.mgmt.corp.example.local": "example.local",
|
||||
"ntp01.acme.ru": "acme.ru",
|
||||
"a.b.c.example.co.uk": "example.co.uk",
|
||||
"10.20.30.40": "",
|
||||
"localhost": "",
|
||||
"com": "",
|
||||
"*.wildcard.acme.ru": "acme.ru",
|
||||
}
|
||||
for in, want := range cases {
|
||||
if got := registrableDomain(in); got != want {
|
||||
t.Errorf("registrableDomain(%q) = %q, want %q", in, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuessCustomers_RanksStrongEvidence(t *testing.T) {
|
||||
findings := []models.PrivacyFinding{
|
||||
{Category: catResolv, Path: "resolv.conf", Line: 1, Match: "corp.acme.ru", Excerpt: "domain corp.acme.ru"},
|
||||
{Category: catADLDAP, Path: "activedir.conf", Line: 3, Match: "corp.acme.ru", Excerpt: "racdomain=corp.acme.ru"},
|
||||
{Category: catDomain, Path: "audit.log", Line: 9, Match: "noise.other.com", Excerpt: "x noise.other.com"},
|
||||
}
|
||||
got := guessCustomers(findings)
|
||||
if len(got) == 0 || got[0].Domain != "acme.ru" {
|
||||
t.Fatalf("got %+v, want acme.ru first", got)
|
||||
}
|
||||
if got[0].Confidence != "high" {
|
||||
t.Errorf("confidence = %s, want high", got[0].Confidence)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user