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:
Mikhail Chusavitin
2026-09-02 15:24:32 +03:00
co-authored by Claude Sonnet 5
parent 3311bafd8e
commit 4a4910f207
23 changed files with 1466 additions and 33 deletions
+74 -33
View File
@@ -9,19 +9,60 @@ const (
// AnalysisResult contains all parsed data from an archive
type AnalysisResult struct {
Filename string `json:"filename"`
SourceType string `json:"source_type,omitempty"` // archive | api
Protocol string `json:"protocol,omitempty"` // redfish | ipmi
TargetHost string `json:"target_host,omitempty"` // BMC host for live collect
SourceTimezone string `json:"source_timezone,omitempty"` // Source timezone/offset used during collection (e.g. +08:00)
CollectedAt time.Time `json:"collected_at,omitempty"` // Collection/upload timestamp
InventoryLastModifiedAt time.Time `json:"inventory_last_modified_at,omitempty"` // Redfish inventory last modified (InventoryData/Status)
RawPayloads map[string]any `json:"raw_payloads,omitempty"` // Additional source payloads (e.g. Redfish tree)
CollectionErrors []CollectionError `json:"collection_errors,omitempty"` // BMC-reported failures to collect specific sections
Events []Event `json:"events"`
FRU []FRUInfo `json:"fru"`
Sensors []SensorReading `json:"sensors"`
Hardware *HardwareConfig `json:"hardware"`
Filename string `json:"filename"`
SourceType string `json:"source_type,omitempty"` // archive | api
Protocol string `json:"protocol,omitempty"` // redfish | ipmi
TargetHost string `json:"target_host,omitempty"` // BMC host for live collect
SourceTimezone string `json:"source_timezone,omitempty"` // Source timezone/offset used during collection (e.g. +08:00)
CollectedAt time.Time `json:"collected_at,omitempty"` // Collection/upload timestamp
InventoryLastModifiedAt time.Time `json:"inventory_last_modified_at,omitempty"` // Redfish inventory last modified (InventoryData/Status)
RawPayloads map[string]any `json:"raw_payloads,omitempty"` // Additional source payloads (e.g. Redfish tree)
CollectionErrors []CollectionError `json:"collection_errors,omitempty"` // BMC-reported failures to collect specific sections
Events []Event `json:"events"`
FRU []FRUInfo `json:"fru"`
Sensors []SensorReading `json:"sensors"`
Hardware *HardwareConfig `json:"hardware"`
PrivacyScan *PrivacyScan `json:"privacy_scan,omitempty"` // customer-data / anonymization scan of the source files
}
// PrivacyScan is the result of scanning the source files for customer-identifying
// and site-identifying data (DNS suffix, AD domain, NTP/DNS/syslog host names,
// timezone, e-mail, public IPs, cert CN/SAN, FRU location fields). Detection only:
// it never modifies the source. See bible-local/docs/privacy-scan.md.
type PrivacyScan struct {
FilesScanned int `json:"files_scanned"`
Customers []CustomerGuess `json:"customers,omitempty"`
Findings []PrivacyFinding `json:"findings,omitempty"`
Summary PrivacySummary `json:"summary"`
}
// CustomerGuess is a registrable domain that most likely identifies the customer,
// derived from the scan findings.
type CustomerGuess struct {
Domain string `json:"domain"`
Confidence string `json:"confidence"` // high | medium | low
Hits int `json:"hits"`
Evidence []string `json:"evidence,omitempty"` // up to 3 "path:line: excerpt" strings
}
// PrivacyFinding is one matched sensitive token.
type PrivacyFinding struct {
Category string `json:"category"`
Severity string `json:"severity"` // high | medium | low
Path string `json:"path"`
Line int `json:"line,omitempty"`
Match string `json:"match"`
Excerpt string `json:"excerpt,omitempty"`
Hint string `json:"hint,omitempty"`
}
// PrivacySummary is the finding count breakdown.
type PrivacySummary struct {
Total int `json:"total"`
High int `json:"high"`
Medium int `json:"medium"`
Low int `json:"low"`
ByCategory map[string]int `json:"by_category,omitempty"`
}
// CollectionError represents a BMC-reported failure to collect a specific data section.
@@ -263,26 +304,26 @@ type MemoryDIMM struct {
// Storage represents a storage device
type Storage struct {
Slot string `json:"slot"`
Type string `json:"type"`
Model string `json:"model"`
Description string `json:"description,omitempty"`
SizeGB int `json:"size_gb"`
SerialNumber string `json:"serial_number,omitempty"`
Manufacturer string `json:"manufacturer,omitempty"`
Firmware string `json:"firmware,omitempty"`
Interface string `json:"interface,omitempty"`
Present bool `json:"present"`
Location string `json:"location,omitempty"` // Front/Rear
BackplaneID int `json:"backplane_id,omitempty"`
VendorID int `json:"vendor_id,omitempty"` // PCI Vendor ID (decimal), NVMe drives only
DeviceID int `json:"device_id,omitempty"` // PCI Device ID (decimal), NVMe drives only
RemainingEndurancePct *int `json:"remaining_endurance_pct,omitempty"` // 0-100 %; nil = not reported
LogicalBlockSizeBytes int64 `json:"logical_block_size_bytes,omitempty"`
PhysicalBlockSizeBytes int64 `json:"physical_block_size_bytes,omitempty"`
MetadataBytesPerBlock int64 `json:"metadata_bytes_per_block,omitempty"`
Status string `json:"status,omitempty"`
Details map[string]any `json:"details,omitempty"`
Slot string `json:"slot"`
Type string `json:"type"`
Model string `json:"model"`
Description string `json:"description,omitempty"`
SizeGB int `json:"size_gb"`
SerialNumber string `json:"serial_number,omitempty"`
Manufacturer string `json:"manufacturer,omitempty"`
Firmware string `json:"firmware,omitempty"`
Interface string `json:"interface,omitempty"`
Present bool `json:"present"`
Location string `json:"location,omitempty"` // Front/Rear
BackplaneID int `json:"backplane_id,omitempty"`
VendorID int `json:"vendor_id,omitempty"` // PCI Vendor ID (decimal), NVMe drives only
DeviceID int `json:"device_id,omitempty"` // PCI Device ID (decimal), NVMe drives only
RemainingEndurancePct *int `json:"remaining_endurance_pct,omitempty"` // 0-100 %; nil = not reported
LogicalBlockSizeBytes int64 `json:"logical_block_size_bytes,omitempty"`
PhysicalBlockSizeBytes int64 `json:"physical_block_size_bytes,omitempty"`
MetadataBytesPerBlock int64 `json:"metadata_bytes_per_block,omitempty"`
Status string `json:"status,omitempty"`
Details map[string]any `json:"details,omitempty"`
StatusCheckedAt *time.Time `json:"status_checked_at,omitempty"`
StatusChangedAt *time.Time `json:"status_changed_at,omitempty"`