feat(privacy): detect an already-sanitized source

Scan now reports PrivacyScan.Sanitized {detected, markers, strong, files,
evidence}. SanitizationMarkers recognises a value slot filled with one
repeated placeholder + separators (xxxxx.xxxx.xx, x@xxxx.xxxx.xx,
000.00.00.0, a decoy timezone) - it matches the shape, not the literal "x",
so evolving the redaction mechanism still trips it.

detected requires corroboration: strong>=2, or strong>=1 && markers>=3, or
markers>=4. A single filler-looking token is reported (markers:1) but never
asserted as sanitized, so a partial future pass or a coincidence does not
read as "done". 0.0.0.0 / 000 / UTC / Etc/UTC are too plausibly intentional
and do not count.

UI: the Customer-data panel shows "файл уже обезличен" and hides the
sanitize button when detected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014jDYM1nnoZZ3vFz23DDaV1
This commit is contained in:
Mikhail Chusavitin
2026-09-03 10:02:02 +03:00
co-authored by Claude Sonnet 5
parent a63bb17438
commit f38fb2de69
10 changed files with 334 additions and 10 deletions
+12
View File
@@ -34,6 +34,18 @@ type PrivacyScan struct {
Customers []CustomerGuess `json:"customers,omitempty"`
Findings []PrivacyFinding `json:"findings,omitempty"`
Summary PrivacySummary `json:"summary"`
Sanitized *SanitizedReport `json:"sanitized,omitempty"` // filler markers left by internal/sanitize
}
// SanitizedReport says whether the source already looks de-identified. Detected
// requires corroboration - a single filler-looking token is not enough, so
// evolving the redaction mechanism does not trip false positives.
type SanitizedReport struct {
Detected bool `json:"detected"`
Markers int `json:"markers"`
Strong int `json:"strong"`
Files int `json:"files"`
Evidence []string `json:"evidence,omitempty"`
}
// CustomerGuess is a registrable domain that most likely identifies the customer,