Skip to main content

Local-first privacy anonymizer for Italian PII (GDPR-aware).

Project description

AI Privacy Anonymizer

Versione: 0.2.1
Autore: Sergio Dogliani
Licenza: MIT
Python: ≥ 3.11

Strumento Python locale per rilevare e mascherare automaticamente dati personali (PII) da documenti di vario formato prima di caricarli su chatbot AI (Claude, ChatGPT, Gemini, ecc.) senza rischi di data leakage. Tutto avviene localmente: nessun dato lascia il dispositivo durante l'anonimizzazione.

Scarica l'ultima release su GitHub — include l'eseguibile Windows precompilato (nessuna installazione di Python richiesta).


Installazione e utilizzo rapido

# Installa tutto (ML, PDF, Office, Web UI, API REST)
pip install "ai-privacy-anonymizer[recommended]"

# Anonimizza un singolo file
privacy-anonymizer documento.pdf

# Anonimizza tutti i file in una cartella
privacy-anonymizer ./cartella_input/ --output ./cartella_output/

# Avvia l'interfaccia Web locale (nessuna installazione di Node.js richiesta)
privacy-anonymizer-web

L'output viene salvato nella stessa cartella del file originale con il suffisso _anonymized. Per la Web UI, apri il browser all'indirizzo indicato nel terminale (di solito http://127.0.0.1:7860).


Indice


Architettura ibrida a 3 livelli

Il progetto adotta un'architettura ibrida che combina tre rilevatori complementari per massimizzare il recall (priorità rispetto alla precision nel caso d'uso pre-chatbot):

INPUT FILE
    │
    ▼
TEXT SEGMENTER ─── divide in chunk rispettando i confini di frase
    │
    ├─────────────────────┬─────────────────────┐
    ▼                     ▼                     ▼
LAYER 1               LAYER 2               LAYER 3
OpenAI OPF            GLiNER                Presidio Pattern IT
8 categorie           60+ categorie         Regex + checksum
contesto semantico    italiano nativo       deterministico
    │                     │                     │
    └─────────────────────┴─────────────────────┘
                          │
                    SPAN RESOLVER
              (merge, deduplication, priorità)
                          │
                    MASKING ENGINE
                          │
                 FILE RECONSTRUCTOR
                          │
              OUTPUT FILE + AUDIT LOG JSON

Layer 1 — OpenAI Privacy Filter (OPF)

  • Modello basato su Transformer con finestra di contesto fino a 128K token
  • Rileva 8 categorie semantiche: private_person, private_email, private_phone, private_address, private_date, private_url, account_number, secret
  • Decoder Viterbi configurabile per alto recall:
    • conservative: parametri di default (alta precision)
    • balanced: background_stay=-2.0, background_to_start=+1.5, span_continuation=+1.0
    • aggressive: background_stay=-3.0, background_to_start=+2.0, span_continuation=+1.5
  • Unico layer con categoria nativa SECRET per password, API key, token JWT, valori .env
  • Installazione esterna richiesta (vedi sezione Installazione)

Layer 2 — GLiNER gliner_multi_pii-v1

  • Modello zero-shot fine-tuned su italiano per riconoscimento entità named
  • Oltre 60 categorie PII comprese quelle assenti in OPF: passport_number, driver_license, health_insurance_id, medical_condition, credit_card_number, cvv, blood_type, username, digital_signature, organization
  • Threshold configurabile (default: 0.3) per bilanciare recall e precision
  • Download automatico del modello (~300 MB) al primo utilizzo
  • Licenza Apache 2.0; installazione via extra [ml]

Layer 3 — Pattern Recognizer italiani (deterministico)

Regex con validazione checksum dove applicabile. Attivo per default senza dipendenze extra.

Entità Validazione Esempio
CODICE_FISCALE ✅ Checksum controllo carattere Luhn-like RSSMRA80A01L219M
PARTITA_IVA ✅ Algoritmo mod-11 01114601006
IBAN_IT ✅ Algoritmo IBAN ISO 7064 mod-97-10 IT60X0542811101000000123456
TARGA_IT Pattern (auto + moto) AB123CD
CARTA_IDENTITA Pattern (AA1234567 o CA1234567AB) AX1234567
CELL_IT Pattern (prefisso 3xx, opz. +39/0039) 3401234567
TEL_IT Pattern fisso (prefisso 0, opz. +39/0039) 011 1234567
EMAIL Pattern RFC-like mario@esempio.it
PEC Pattern email + domini .pec. o .pec.it studio@legalmail.pec.it
TESSERA_SANITARIA Pattern 20 cifre con prefisso 80 80380030001234567890
MATRICOLA_INPS Pattern 8-9 cifre con parola chiave di contesto 12345678 (dopo "matricola INPS")
IP_ADDRESS Pattern IPv4 con validazione ottetti 0-255 192.168.1.10
INDIRIZZO Pattern (Via/Corso/Piazza/Viale/Vicolo/Largo + nome + numero civico) Via Roma 12
DOCUMENTO_ID Pattern (ID- + 6-12 caratteri alfanumerici) ID-ABC123456

Categorie PII rilevate

L'insieme completo delle categorie emesse verso il masking engine, dopo normalizzazione dei label dei tre layer:

Categoria normalizzata Sorgente principale Note
PERSONA OPF + GLiNER Nomi propri con context-awareness
EMAIL L3 + OPF Mailbox standard
PEC L3 Posta Elettronica Certificata
TELEFONO / CELL_IT / TEL_IT L3 + OPF Numeri IT e internazionali
INDIRIZZO L3 + OPF + GLiNER Indirizzi stradali
DATA_PRIVATA OPF + GLiNER Date di nascita e date private
URL OPF + GLiNER URL con path personale
ACCOUNT_NUMBER OPF Numero conto corrente generico
SECRET OPF Password, API key, token, segreti
CODICE_FISCALE L3 + GLiNER Con validazione checksum
PARTITA_IVA L3 Con validazione mod-11
IBAN_IT L3 + GLiNER Con validazione ISO
TARGA_IT L3 Targhe autoveicoli
CARTA_IDENTITA L3 CIE e documenti identità
TESSERA_SANITARIA L3 + GLiNER Tessera sanitaria e TEAM
MATRICOLA_INPS L3 Con context words
IP_ADDRESS L3 + GLiNER Indirizzi IPv4 validi
USERNAME GLiNER Handle e nomi utente
PASSAPORTO GLiNER Numero passaporto
PATENTE GLiNER Patente di guida
CARTA_CREDITO GLiNER Numero carta di credito
CONDIZIONE_MEDICA GLiNER Diagnosi e condizioni cliniche
ORGANIZZAZIONE GLiNER Nome azienda privata in contesto
TAX_ID GLiNER Identificativo fiscale generico

Formati file supportati

Round-trip completo (stesso formato in input e output)

Formato Estensioni Parsing Ricostruzione Note
Testo puro .txt .md .log .csv built-in built-in Lettura/scrittura UTF-8 diretta
Word .docx python-docx python-docx Paragrafi + intestazioni + piè di pagina + tabelle + commenti
Excel .xlsx openpyxl openpyxl Celle (stringhe) + nomi foglio + commenti autore
PowerPoint .pptx python-pptx python-pptx Testo shape + note relatore
PDF (selezionabile o immagine) .pdf pypdf + RapidOCR (ONNX) PyMuPDF overlay / redazione OCR Se il testo selezionabile è ≥ 20 caratteri: redazione a coordinate. Sotto soglia (es. solo "Pagina 1 di 1"): OCR automatico come per PDF scansionati
Immagini .png .jpg .jpeg .tiff .bmp RapidOCR (ONNX) Pillow Redazione a coordinate OCR; fallback a immagine testo plano
Email .eml stdlib email stdlib email From/To/Cc/Subject + body
XML/FatturaPA .xml xml.etree xml.etree Testo e attributi; struttura XML preservata
JSON .json built-in built-in Valori stringa (foglie); struttura, numeri e booleani preservati
RTF .rtf striprtf built-in minimal Ricostruzione RTF semplificata

Solo lettura (output .txt anonimizzato)

Formato Estensioni Dipendenza
Outlook MSG .msg extract-msg (extra documents)
Word legacy .doc best-effort binary (suggerito LibreOffice)
Excel legacy .xls xlrd (extra documents)

Modalità di mascheratura

Modalità Output esempio Caso d'uso
replace (default) [CF_1], [EMAIL_2], [PERSONA_1] Upload chatbot — contesto leggibile
redact ████████████████ Documenti da condividere con terzi
generalize [CF], [EMAIL], [PERSONA] Quando la numerazione progressiva è superflua
hash [SHA256:a3f2c1d4e5f6] Pipeline tecniche con eventuale de-anonimizzazione

Tutte le modalità supportano il consistent mapping: la stessa entità riceve lo stesso placeholder in tutto il documento (es. ogni occorrenza di "Mario Rossi", incluse varianti parziali, diventa sempre [PERSONA_1]).


Installazione

Base (solo Layer 3 — pattern italiani, nessun ML)

pip install ai-privacy-anonymizer

Con supporto Office (DOCX, XLSX, PPTX)

pip install "ai-privacy-anonymizer[office]"

Con supporto documenti (PDF, immagini OCR, EML, MSG, XLS, RTF)

pip install "ai-privacy-anonymizer[documents]"

Con Layer 2 GLiNER

pip install "ai-privacy-anonymizer[ml]"

Con Web UI Gradio

pip install "ai-privacy-anonymizer[webui]"

Con API REST FastAPI

pip install "ai-privacy-anonymizer[api]"

Setup standard (extra [recommended])

pip install "ai-privacy-anonymizer[recommended]"

Installa office, documents, ml (GLiNER), webui, api, rich. È la scelta consigliata per la maggior parte degli utenti. Il Layer 1 (OPF) può essere aggiunto separatamente (vedi sotto).

Installazione con OPF (Layer 1 — opzionale)

OPF (OpenAI Privacy Filter) non è distribuibile come extra PyPI perché risiede solo su GitHub. Per abilitarlo, installalo manualmente dopo l'extra [recommended]:

pip install "ai-privacy-anonymizer[recommended]"
pip install "opf @ git+https://github.com/openai/privacy-filter"

Oppure con il comando integrato:

privacy-anonymizer --install-full

Richiede ~5 GB totali tra dipendenze e modelli (3 GB solo per OPF).

Sviluppo locale

git clone https://github.com/sedoglia/AI-Privacy-Anonymizer.git
cd AI-Privacy-Anonymizer
pip install -e ".[dev,office,documents,ml,api]"

# Esegui tutta la suite (esclude i test che richiedono il server avviato)
pytest

# Solo i test dell'API REST (senza avviare il server)
pytest tests/api/ --ignore=tests/api/test_live.py

# Test con server live (avviare prima: privacy-anonymizer --api)
pytest tests/api/test_live.py --live

Verifica setup

privacy-anonymizer --setup

Eseguibile Windows (EXE standalone)

File .exe autonomo per Windows — nessuna installazione di Python richiesta.

Download precompilato (consigliato)

⬇ Scarica privacy-anonymizer.exe (Google Drive, ~2.68 GB)

Il file (~2.68 GB) è distribuito tramite Google Drive perché supera il limite di 2 GB di GitHub.

Dopo il download, eseguire dal terminale:

privacy-anonymizer.exe documento.pdf

Alla prima esecuzione vengono scaricati automaticamente i modelli ML (~500 MB).

Compilare da sorgente

Per rigenerare l'eseguibile in locale:

pyinstaller dist/privacy_anonymizer.spec --distpath dist --workpath build/pyinstaller

Output: dist/privacy-anonymizer.exe (~2.68 GB). Il bundle include tutti e tre i layer ML (OPF, GLiNER, pattern), RapidOCR, tutti gli adattatori di formato e il runtime Python.

I file di build sono versionati in dist/:

File Scopo
dist/privacy_anonymizer.spec Specifica PyInstaller (hiddenimports, runtime hooks)
dist/hooks/rthook_tiktoken.py Runtime hook: corregge il bug di tiktoken nei bundle PyInstaller (namespace package tiktoken_ext non trovato da pkgutil.iter_modules)

Nota: dist/privacy-anonymizer.exe è escluso dal repository (troppo grande). Va rigenerato localmente con il comando sopra.


Utilizzo — CLI

Anonimizzare un singolo file

privacy-anonymizer documento.docx

Output: documento_anonymized.docx nella stessa cartella, più documento_anonymized.docx.audit.json.

Specificare file o cartella di output

privacy-anonymizer documento.docx --output /percorso/output/
privacy-anonymizer documento.docx --output documento_clean.docx

Anonimizzare una cartella intera

privacy-anonymizer ./documenti/ --output ./documenti_clean/
# Con ricorsione disabilitata
privacy-anonymizer ./documenti/ --output ./out/ --no-recursive

Testo diretto da riga di comando

privacy-anonymizer --text "Mario Rossi, CF RSSMRA80A01L219M, tel 3401234567"

Modalità di mascheratura

privacy-anonymizer report.pdf --mode redact
privacy-anonymizer contratto.docx --mode generalize
privacy-anonymizer dati.xlsx --mode hash

Gestire i layer attivi

Lo stack ibrido completo (OPF + GLiNER + pattern) è attivo per default. Per ridurre i layer:

# Solo GLiNER + pattern (senza OPF)
privacy-anonymizer file.txt --disable-layer opf

# Solo OPF + pattern (senza GLiNER)
privacy-anonymizer file.txt --disable-layer gliner

# Solo pattern italiani (più veloce, nessuna dipendenza ML)
privacy-anonymizer file.txt --pattern-only

Configurazione recall OPF

# Modalità conservative (alta precision, meno recall)
privacy-anonymizer file.txt --recall-mode conservative

# Modalità balanced (bilanciato per uso chatbot)
privacy-anonymizer file.txt --recall-mode balanced

# Modalità aggressive (default — massimo recall)
privacy-anonymizer file.txt --recall-mode aggressive

Configurazione GLiNER

# Soglia di confidenza (default: 0.3 — valori più alti aumentano precision, riducono recall)
privacy-anonymizer file.txt --gliner-threshold 0.5

# Modello alternativo
privacy-anonymizer file.txt --gliner-model urchade/gliner_multi_pii-v1

Dry-run (analisi senza scrivere output)

privacy-anonymizer contratto.docx --dry-run

Mostra span rilevati, categorie e conteggi senza produrre file.

Mappa entità (categorie e placeholder, senza valori originali)

privacy-anonymizer contratto.docx --show-map

Esempio output:

Mappa entità (categorie e placeholder, nessun valore originale):
  [CF_1]       ←  CODICE_FISCALE
  [EMAIL_1]    ←  EMAIL
  [PERSONA_1]  ←  PERSONA
  [PIVA_1]     ←  PARTITA_IVA

Report compliance GDPR (PDF)

privacy-anonymizer documento.docx --compliance-report report_gdpr.pdf

Output audit in JSON

privacy-anonymizer documento.docx --json

Export entity vault (per de-anonimizzazione in modalità hash)

privacy-anonymizer documento.txt --mode hash --export-vault vault.json

vault.json contiene il mapping placeholder → {label, originale}. Conservare in modo sicuro.

Metadata

# Disabilita la rimozione metadati
privacy-anonymizer documento.docx --keep-metadata

Performance e memoria

# Elaborazione low-memory: layer in sequenza, libera RAM tra uno e l'altro
privacy-anonymizer file.txt --low-memory

# Parallelizzazione: layer su thread separati (incompatibile con --low-memory)
privacy-anonymizer file.txt --parallel

# Device ML: auto (default, usa CUDA/MPS se disponibili), cpu, cuda, mps
privacy-anonymizer file.txt --device auto
privacy-anonymizer file.txt --device cpu

Acceleratori disponibili

Flag Effetto Quando usarlo
--device auto (default) Sceglie CUDA → MPS → CPU in base alla disponibilità Sempre, se non sai cosa scegliere
--ocr-dpi 200 Riduce DPI di rendering OCR da 300 → 200 (~50% più veloce, leggera perdita di qualità) PDF scansionati con testo grande/leggibile
--no-ocr-parallel-pages Disattiva l'OCR parallelo per pagina Se la macchina ha poca RAM
--ocr-max-workers N Numero max di thread per OCR pagine in parallelo (default 4) Adatta al numero di core CPU disponibili
--no-chunk-long-text Disattiva il chunking parallelo dei testi lunghi per i layer ML Per debugging o per testi <4 KB
--chunk-threshold N Lunghezza minima per attivare il chunking (default 4000 caratteri) Aumenta per evitare chunking su testi medi
--chunk-size N Lunghezza in caratteri di ogni finestra (default 1500) Riduci se il modello tronca, aumenta se vuoi più contesto
--chunk-overlap N Sovrapposizione tra finestre (default 100) Aumenta se vedi entità tagliate ai bordi
--chunk-max-workers N Thread per processare i chunk in parallelo (default 4) Adatta al numero di core CPU
--ml-skip-extensions ".log,.txt" Estensioni per cui saltare i layer ML (GLiNER/OPF) sopra --ml-skip-min-chars File di log/dump dove i pattern bastano
--ml-skip-min-chars N Soglia in caratteri sopra cui scatta lo skip ML (default 8000) File piccoli passano comunque per i layer ML
# OCR più veloce su PDF scansionati: 200 DPI + 8 thread per pagine in parallelo
privacy-anonymizer scansione.pdf --ocr-dpi 200 --ocr-max-workers 8

# Log file lunghi: solo pattern, salta GLiNER/OPF (drasticamente più veloce)
privacy-anonymizer applicazione.log --ml-skip-extensions ".log,.txt" --ml-skip-min-chars 4000

# Disattiva chunking se vuoi che GLiNER/OPF vedano l'intero testo
privacy-anonymizer documento.txt --no-chunk-long-text

De-anonimizzazione da vault

# 1. Anonimizza con modalità hash + esporta vault
privacy-anonymizer documento.txt --mode hash --export-vault vault.json --output anon.txt

# 2. Ripristina il testo originale dal vault
privacy-anonymizer --restore vault.json anon.txt --output restored.txt

Log verboso (diagnostica)

Per default, tutti i messaggi informativi delle librerie esterne (RapidOCR, transformers, ecc.) vengono soppressi per mantenere l'output pulito. Usando --log si attiva la modalità di log verboso: tutti i messaggi (INFO, DEBUG, WARNING) vengono scritti su file, inclusi quelli delle librerie esterne.

# Log verboso con nome file generato automaticamente (privacy_anonymizer_YYYYMMDD_HHMMSS.log)
privacy-anonymizer documento.pdf --log

# Log verboso su file specificato
privacy-anonymizer ./cartella/ --output ./out/ --log /tmp/debug.log

Il file di log include timestamp, nome del logger, file sorgente e numero di riga per ogni messaggio. Il comportamento della console rimane invariato (solo output essenziale).

Manutenzione

# Cancella cache locale di modelli/parser
privacy-anonymizer --wipe-cache

# Mostra formati supportati
privacy-anonymizer --supported-formats

# Verifica setup e dipendenze
privacy-anonymizer --setup
privacy-anonymizer --download-models --verbose

Dataset sintetico e valutazione

# Genera dataset sintetico JSONL
privacy-anonymizer --generate-synthetic-dataset ./synthetic.jsonl

# Valuta un dataset JSONL con campi "text" e "labels"
privacy-anonymizer --evaluate ./synthetic.jsonl

Utilizzo — Python API

from privacy_anonymizer import Anonymizer, LayerConfig

# Configurazione personalizzata
config = LayerConfig(
    opf_enabled=False,             # richiede installazione OPF esterna
    opf_recall_mode="balanced",    # "conservative" | "balanced" | "aggressive"
    gliner_enabled=True,           # richiede extra [ml]
    gliner_model="urchade/gliner_multi_pii-v1",
    gliner_threshold=0.5,
    pattern_enabled=True,
    masking_mode="replace",        # "replace" | "redact" | "generalize" | "hash"
    consistent_mapping=True,
    keep_metadata=False,
    recursive=True,
    low_memory=False,
)

anon = Anonymizer(config=config, device="cpu")

# ── Testo diretto ──────────────────────────────────────────────
masked_text, counts = anon.process_text(
    "Mario Rossi, CF RSSMRA80A01L219M, tel 3401234567",
    language="it",
)
# masked_text → "Mario Rossi, [CF_1], [TEL_1]"
# counts → {"CODICE_FISCALE": 1, "CELL_IT": 1}

# Oppure con accesso completo al risultato
result = anon.analyze_text("Mario Rossi, mario@example.com")
print(result.anonymized_text)
print(result.audit_report)
# Entity vault (solo se mode=hash)
vault = result.replacements and [r.__dict__ for r in result.replacements]

# ── Singolo file ───────────────────────────────────────────────
result = anon.process_file("input.docx")
result.save("output.docx")
print(result.audit_report)

# Con output_path esplicito
result = anon.process_file("input.pdf", output_path="clean/output.pdf")

# Dry-run
result = anon.process_file("input.xlsx", dry_run=True)

# ── Batch su cartella ──────────────────────────────────────────
batch = anon.process_folder("./docs_in/", output_dir="./docs_out/")
print(f"Processati: {batch.processed_count}")
print(f"Saltati: {batch.skipped_count}")
for path, reason in batch.skipped:
    print(f"  SKIP {path}: {reason}")

# ── Rilevamento span senza mascheratura ────────────────────────
spans = anon.detect_text("Mario Rossi, IBAN IT60X0542811101000000123456")
for span in spans:
    print(span.start, span.end, span.label, span.source, span.score)

# ── MaskingPlan con entity vault ───────────────────────────────
from privacy_anonymizer.masking import build_masking_plan
plan = build_masking_plan(text, spans, mode="hash")
vault = plan.entity_vault()  # {placeholder: {label, original}}

Modello dati: DetectionSpan

@dataclass
class DetectionSpan:
    start: int           # offset carattere inizio (inclusivo)
    end: int             # offset carattere fine (esclusivo)
    label: str           # categoria normalizzata (es. "CODICE_FISCALE")
    source: str          # "pattern" | "opf" | "gliner"
    score: float         # confidenza (1.0 per pattern deterministico)
    metadata: dict       # {"checksum_valid": "true"/"false"} per CF

    @property
    def length(self) -> int: ...          # end - start

    def overlaps_or_touches(
        self, other: "DetectionSpan", max_gap: int = 0
    ) -> bool: ...                        # True se gap fra i due span ≤ max_gap

Web UI locale (Gradio)

privacy-anonymizer --webui
# oppure
privacy-anonymizer-web

Apre http://127.0.0.1:7860 con:

  • Tab Testo: input testuale libero, selezione modalità, checkbox GLiNER, output + audit JSON
  • Tab File: drag & drop file, stesse opzioni, download file anonimizzato

Non richiede connessione Internet durante l'uso. Richiede pip install -e .[webui].


API REST locale (FastAPI)

privacy-anonymizer --api
# oppure
privacy-anonymizer-api

Avvia il server su http://127.0.0.1:8000. Richiede pip install -e .[api].

Endpoint disponibili

Metodo Path Descrizione
GET /health Healthcheck — restituisce {"status": "ok"}
POST /anonymize/text Anonimizza testo (form: text, mode, hybrid)
POST /anonymize/file Anonimizza file (multipart: file, mode, hybrid)

Esempio con curl

# Testo
curl -X POST http://127.0.0.1:8000/anonymize/text \
  -F "text=Mario Rossi, CF RSSMRA80A01L219M" \
  -F "mode=replace"

# File
curl -X POST http://127.0.0.1:8000/anonymize/file \
  -F "file=@documento.docx" \
  -F "mode=redact" \
  --output documento_redacted.docx

Documentazione interattiva Swagger disponibile su http://127.0.0.1:8000/docs.


MCP Server stdio

Integrazione come strumento MCP (Model Context Protocol) per Claude Desktop e altri client compatibili.

privacy-anonymizer-mcp

Il server legge richieste JSON-RPC da stdin e scrive risposte su stdout (protocollo MCP 2024-11-05).

Tool esposto: anonymize_text

{
  "method": "tools/call",
  "params": {
    "name": "anonymize_text",
    "arguments": { "text": "Mario Rossi, mario@example.com" }
  }
}

Configurazione in claude_desktop_config.json:

{
  "mcpServers": {
    "privacy-anonymizer": {
      "command": "privacy-anonymizer-mcp"
    }
  }
}

Span Resolver e mapping consistente

Il resolver gestisce la fusione degli span prodotti dai tre layer con le seguenti regole:

Priorità sorgente

Layer 3 (pattern deterministico) > Layer 1 (OPF) > Layer 2 (GLiNER)

Quando due span si sovrappongono esattamente, vince quello con priorità maggiore.

Casi di fusione

  • Span identici: deduplicati, mantiene quello a priorità maggiore
  • Span annidati / sovrapposti: vince lo span più ampio (es. [Mario] + [Mario Rossi][Mario Rossi])
  • Span adiacenti compatibili (gap ≤ 3 caratteri): fusi in un unico span della stessa categoria semantica
  • Conflitto di tipo: se L3 valida il checksum, vince L3; altrimenti vince il layer a priorità maggiore

Consistent entity mapping

"Mario Rossi"      → [PERSONA_1]   (tutte le occorrenze, incluse varianti parziali)
"mario@azienda.it" → [EMAIL_1]
"RSSMRA80A01L219M" → [CF_1]

La normalizzazione delle varianti (case-insensitive, whitespace collassato) garantisce che la stessa entità riceva sempre lo stesso placeholder nel documento. La mappa è mantenuta solo in RAM durante l'esecuzione e mai scritta su disco, salvo uso esplicito di --export-vault.

Filtro falsi positivi

Il resolver applica automaticamente tre livelli di protezione contro i falsi positivi dei modelli ML:

  • Punteggiatura strutturale: i separatori CSV/tabellari (virgola, punto e virgola, tab) vengono rimossi dai bordi degli span OPF/GLiNER prima della fusione, così la virgola tra nome ed e-mail non viene inglobata nel placeholder.
  • Parole non-PII: una lista di termini esclusi copre intestazioni di colonne, nomi di città italiane, mansioni lavorative e nomi di prodotti/dispositivi comuni (es. Mouse, Tastiera, Laptop, Monitor) che i modelli ML tendono a classificare erroneamente come PERSONA.
  • URL non validi: gli span classificati come URL da GLiNER/OPF vengono scartati se il testo non contiene pattern URL reali (http://, www., dominio con TLD). Questo evita che parole comuni come Laptop vengano mascherate come [URL_1].

Gestione metadati

I metadati dei file Office e PDF vengono rimossi per default (disattivabile con --keep-metadata):

Campo Formato Azione
Autore (Author) DOCX, XLSX, PPTX, PDF Sostituito con "Anonimo"
LastModifiedBy DOCX, XLSX, PPTX Sostituito con "Anonimo"
Organizzazione (Company) XLSX Rimosso
Titolo, Oggetto, Parole chiave DOCX, XLSX, PPTX Svuotati
Commenti documento DOCX, XLSX, PPTX Azzerati (autore sostituito)
Metadati XMP / Info dict PDF Rimossi via PyMuPDF
EXIF / XMP JPEG, TIFF, PNG Strip completo (immagine ricostruita)
Autore commento cella XLSX Sostituito con "Anonimo"

Audit log JSON

Ogni elaborazione produce un file .audit.json nella stessa posizione del file output. Il log non contiene mai i valori PII originali, solo categorie e conteggi.

{
  "tool_version": "0.2.0",
  "source_file": "contratto_fornitura.docx",
  "output_file": "contratto_fornitura_anonymized.docx",
  "processed_at": "2026-04-30T14:32:01+00:00",
  "processing_time_seconds": 12.4,
  "layers_used": ["opf", "gliner", "pattern"],
  "opf_recall_mode": "balanced",
  "low_memory": false,
  "entities_found": {
    "opf_spans": 12,
    "gliner_spans": 4,
    "pattern_spans": 3,
    "merged_unique_spans": 17,
    "by_category": {
      "PERSONA": 4,
      "EMAIL": 2,
      "CODICE_FISCALE": 1,
      "PARTITA_IVA": 1,
      "TELEFONO_IT": 1,
      "INDIRIZZO": 2,
      "DATA_PRIVATA": 3,
      "SECRET": 2,
      "IP_ADDRESS": 1
    }
  },
  "metadata_stripped": true,
  "track_changes_accepted": true,
  "warnings": []
}

Report compliance GDPR (PDF)

privacy-anonymizer documento.docx --compliance-report report_gdpr.pdf

Genera un PDF (via ReportLab) con:

  • Riferimenti file sorgente e output
  • Timestamp di elaborazione
  • Layer utilizzati e recall mode
  • Elenco categorie PII rilevate con conteggi
  • Flag metadati rimossi
  • Warnings di elaborazione
  • Estratto dell'audit JSON (troncato a 1500 caratteri)

Richiede pip install -e .[documents] (ReportLab).


Dataset sintetico ed evaluation

Generare un dataset sintetico

privacy-anonymizer --generate-synthetic-dataset ./synthetic.jsonl

Ogni riga è un oggetto JSON con campi text (testo di test) e labels (lista di categorie attese):

{"text": "Mario Rossi CF RSSMRA80A01L219M email mario.rossi@example.com tel 3401234567", "labels": ["CODICE_FISCALE", "EMAIL", "TELEFONO_IT"]}
{"text": "P.IVA 01114601006 IBAN IT60X0542811101000000123456 targa AB123CD", "labels": ["PARTITA_IVA", "IBAN_IT", "TARGA_IT"]}
{"text": "Server 192.168.1.10, PEC studio.rossi@legalmail.pec.it", "labels": ["IP_ADDRESS", "PEC"]}

Valutare un dataset

privacy-anonymizer --evaluate ./synthetic.jsonl

Output JSON con metriche:

{
  "documents": 3,
  "expected_labels": 8,
  "matched_labels": 8,
  "extra_labels": 0,
  "precision": 1.0,
  "recall": 1.0,
  "f1": 1.0
}

Da Python

from privacy_anonymizer.evaluation import evaluate_dataset, write_synthetic_dataset
from privacy_anonymizer import Anonymizer, LayerConfig

write_synthetic_dataset("./my_dataset.jsonl")

anon = Anonymizer(LayerConfig(gliner_enabled=True))
metrics = evaluate_dataset("./my_dataset.jsonl", anonymizer=anon)
print(f"F1: {metrics.f1:.2%}")

Entity vault per de-anonimizzazione

In modalità hash, ogni valore PII viene sostituito con [SHA256:xxxxxxxx]. Per mantenere la possibilità di de-anonimizzazione, usare --export-vault:

privacy-anonymizer documento.txt --mode hash --export-vault vault.json

vault.json esempio:

{
  "[SHA256:a3f2c1d4e5f6]": {
    "label": "CODICE_FISCALE",
    "original": "RSSMRA80A01L219M"
  },
  "[SHA256:9b1c3e7f2a4d]": {
    "label": "EMAIL",
    "original": "mario@azienda.it"
  }
}

Nota di sicurezza: il vault contiene i valori originali in chiaro. Conservarlo su storage cifrato, separato dal documento anonimizzato, e cancellarlo quando non più necessario.

Da Python:

from privacy_anonymizer.masking import build_masking_plan
plan = build_masking_plan(text, spans, mode="hash")
vault = plan.entity_vault()  # dict {placeholder: {label, original}}

Requisiti di sistema

Requisito Minimo (solo L3) Con L2 GLiNER Con L1 OPF
Python 3.11 3.11 3.11
RAM 512 MB 2 GB 8 GB
Storage modelli ~300 MB ~3.3 GB
OS Windows 10 / Ubuntu 20.04 / macOS 12 stesso stesso
GPU Non necessaria Opzionale (CUDA 11.8+) Opzionale (4 GB VRAM)
OCR engine RapidOCR (ONNX, in [documents]) RapidOCR (ONNX, in [documents])
LibreOffice Opzionale (per .doc legacy)

Limitazioni note

Limitazione Impatto Mitigazione
OPF recall basso con parametri default PII non rilevate Usa --recall-mode balanced o aggressive
PDF scansionati: qualità OCR dipendente da DPI Testo non riconosciuto Scansionare a ≥ 200 DPI; audit log avvisa se DPI basso
Ricostruzione DOCX con stili complessi Perdita formattazione in rari casi Fallback a .txt con warning in audit log
GLiNER non è L1: F1 ~81% vs ~96% OPF su benchmark EN Falsi negativi su categorie non-OPF Layer complementare: copre categorie assenti in OPF
EML/MSG: allegati non processati ricorsivamente PII negli allegati non rilevate Audit log avvisa; processare gli allegati separatamente
DOCX track-changes: revisioni accettate ma non cancellate esplicitamente Dati residui nel documento Usare Word per "Accetta tutto" prima dell'export finale
Testo in immagini incorporate in DOCX/PPTX Non analizzato nel passaggio testo Estrarre le immagini manualmente e processarle come file separati
Stack ibrido (tutti e 3 i layer): ~5-6 GB RAM, ~2-3x più lento Impraticabile su hardware limitato --low-memory o --pattern-only

Struttura del progetto

src/privacy_anonymizer/
├── __init__.py              # Esporta Anonymizer, LayerConfig, DetectionSpan, ProcessResult
├── anonymizer.py            # Classe principale Anonymizer + ProcessResult + BatchProcessResult
├── config.py                # LayerConfig, MaskingMode
├── models.py                # DetectionSpan
├── masking.py               # EntityMapper, MaskingPlan, build_masking_plan, mask_text
├── resolver.py              # resolve_spans — merge e deduplication span
├── compliance.py            # write_compliance_report — PDF GDPR
├── evaluation.py            # evaluate_dataset, write_synthetic_dataset
├── errors.py                # MissingOptionalDependencyError
├── cli.py                   # Entry point CLI (argparse)
├── webui.py                 # Web UI Gradio
├── api.py                   # API REST FastAPI
├── mcp_server.py            # MCP stdio server
├── detectors/
│   ├── patterns_it.py       # Layer 3 — pattern italiani + checksum
│   ├── gliner_detector.py   # Layer 2 — GLiNER lazy loader
│   └── opf_detector.py      # Layer 1 — OPF lazy loader + Viterbi config
└── io/
    ├── registry.py          # Registro adapter + get_adapter()
    ├── base.py              # FileAdapter (ABC), FileContent, WriteResult
    ├── text_files.py        # .txt .md .log .csv
    ├── office.py            # .docx .xlsx .pptx
    ├── pdf.py               # .pdf (pypdf + PyMuPDF + ReportLab)
    ├── images.py            # .png .jpg .jpeg .tiff .bmp (Pillow + RapidOCR)
    ├── email_files.py       # .eml .msg
    ├── legacy.py            # .doc .xls .rtf
    ├── xml_files.py         # .xml (FatturaPA)
    └── json_files.py        # .json

tests/
├── conftest.py                  # Fixture condivise: TestClient, sample_dir, live
├── api/
│   ├── test_health.py           # GET /health
│   ├── test_text.py             # POST /anonymize/text (modalità, hybrid, PII italiani)
│   ├── test_file.py             # POST /anonymize/file (TXT, JSON, modalità)
│   ├── test_edge_cases.py       # Robustezza, idempotenza, metodi HTTP non ammessi
│   └── test_live.py             # Test con server live (--live)
├── sample_files/                # File campione TXT/JSON con PII italiani per i test API
├── test_anonymizer.py           # Test Anonymizer end-to-end
├── test_patterns_it.py          # Test pattern + checksum italiani
├── test_patterns_extended.py    # Test unitari per ogni pattern IT (INDIRIZZO, CI, PEC, IPv4, ecc.)
├── test_resolver.py             # Test Span Resolver e filtro falsi positivi
├── test_layer_config.py         # Test LayerConfig, DetectionSpan, chunking, normalize_label
├── test_office_adapters.py      # Test DOCX/XLSX/PPTX
├── test_document_adapters.py    # Test PDF/immagini/EML/RTF
├── test_adapters_legacy.py      # Test LegacyXlsAdapter (xlrd) e RtfAdapter (striprtf)
├── test_json_adapter.py         # Test adapter JSON
├── test_gliner_detector.py      # Test GlinerDetector (mock)
├── test_opf_detector.py         # Test OpfDetector (mock)
├── test_image_redaction.py      # Test redazione coordinate immagini (mock)
├── test_entity_vault.py         # Test Entity Vault: struttura, roundtrip, de-anonimizzazione
├── test_evaluation.py           # Test dataset sintetico e metriche recall/precision/f1
├── test_compliance_report.py    # Test generazione PDF GDPR (ReportLab)
├── test_mcp_server.py           # Test MCP Server stdio: JSON-RPC 2.0, tutti i tipi PII
├── test_cli_flags.py            # Test 32+ flag CLI con dati PII italiani reali
└── test_gap_implementations.py  # Test dataset sintetico, low-memory, vault, MCP, compliance

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ai_privacy_anonymizer-0.2.1.tar.gz (121.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ai_privacy_anonymizer-0.2.1-py3-none-any.whl (70.8 kB view details)

Uploaded Python 3

File details

Details for the file ai_privacy_anonymizer-0.2.1.tar.gz.

File metadata

  • Download URL: ai_privacy_anonymizer-0.2.1.tar.gz
  • Upload date:
  • Size: 121.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for ai_privacy_anonymizer-0.2.1.tar.gz
Algorithm Hash digest
SHA256 9a3fe84698354ded59b98832974edea7ef8f79074c4d6de25d3635a108739ad7
MD5 14e45dd72b5d1703d1f6f9d9dd3dde50
BLAKE2b-256 f2ed5b85f29312843d52d8310d13f4e41811f2ee9da83eae5b3320355b96c2a1

See more details on using hashes here.

File details

Details for the file ai_privacy_anonymizer-0.2.1-py3-none-any.whl.

File metadata

File hashes

Hashes for ai_privacy_anonymizer-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6ac82e3d5d612c2e160cf877a8b64243b6a4a8136647189fd2a3870c1ffd0b4b
MD5 6368d50bbe91afa6183eefddde7b78f0
BLAKE2b-256 1001debdf21aa38a3267d3e11d82ad8eddfa173e48b9dfc07f7de8b518ccf56e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page