Files
Mikhail ChusavitinandClaude Sonnet 5 4a4910f207 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>
2026-09-02 15:24:32 +03:00

86 lines
2.4 KiB
Go

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)
}
}