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:
co-authored by
Claude Sonnet 5
parent
a63bb17438
commit
f38fb2de69
+20
-5
@@ -1441,7 +1441,9 @@ async function loadPrivacyScan() {
|
||||
|
||||
const findings = Array.isArray(data.findings) ? data.findings : [];
|
||||
const customers = Array.isArray(data.customers) ? data.customers : [];
|
||||
if (findings.length === 0 && customers.length === 0) {
|
||||
const san = data.sanitized || null;
|
||||
const alreadySanitized = !!(san && san.detected);
|
||||
if (findings.length === 0 && customers.length === 0 && !alreadySanitized) {
|
||||
section.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
@@ -1451,7 +1453,8 @@ async function loadPrivacyScan() {
|
||||
if (s.high) parts.push(`${s.high} high`);
|
||||
if (s.medium) parts.push(`${s.medium} medium`);
|
||||
if (s.low) parts.push(`${s.low} low`);
|
||||
const lead = customers.length > 0 ? `likely ${customers[0].domain}` : `${findings.length} finding${findings.length > 1 ? 's' : ''}`;
|
||||
const lead = alreadySanitized ? 'файл уже обезличен'
|
||||
: (customers.length > 0 ? `likely ${customers[0].domain}` : `${findings.length} finding${findings.length > 1 ? 's' : ''}`);
|
||||
title.textContent = `Customer data — ${lead}${parts.length ? ' · ' + parts.join(', ') : ''}`;
|
||||
|
||||
if (customers.length > 0) {
|
||||
@@ -1499,11 +1502,23 @@ async function loadPrivacyScan() {
|
||||
|
||||
const sanBox = document.getElementById('privacy-sanitize');
|
||||
if (sanBox) {
|
||||
sanBox.classList.toggle('hidden', !data.sanitizable);
|
||||
sanBox.classList.toggle('hidden', !data.sanitizable && !alreadySanitized);
|
||||
const prev = document.getElementById('privacy-sanitize-preview');
|
||||
if (prev) { prev.classList.add('hidden'); prev.innerHTML = ''; }
|
||||
const btn = document.getElementById('privacy-sanitize-btn');
|
||||
if (btn) { btn.disabled = false; btn.textContent = 'Обезличить и скачать копию'; }
|
||||
if (alreadySanitized) {
|
||||
if (btn) btn.classList.add('hidden');
|
||||
if (prev) {
|
||||
prev.innerHTML = `<div class="privacy-sanitized-note">Файл выглядит уже обезличенным: ` +
|
||||
`${san.markers} маркер(ов) в ${san.files} файл(ах).` +
|
||||
((san.evidence && san.evidence.length)
|
||||
? `<ul class="privacy-evidence">${san.evidence.map(e => `<li>${escapeHtml(e)}</li>`).join('')}</ul>`
|
||||
: '') + `</div>`;
|
||||
prev.classList.remove('hidden');
|
||||
}
|
||||
} else {
|
||||
if (btn) { btn.classList.remove('hidden'); btn.disabled = false; btn.textContent = 'Обезличить и скачать копию'; }
|
||||
if (prev) { prev.classList.add('hidden'); prev.innerHTML = ''; }
|
||||
}
|
||||
}
|
||||
|
||||
section.classList.remove('hidden');
|
||||
|
||||
Reference in New Issue
Block a user