Local-first privacy anonymizer for Italian PII (GDPR-aware).
Project description
AI Privacy Anonymizer
Versione: 0.2.0
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[full]"
# 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
- Installazione e utilizzo rapido
- Architettura ibrida a 3 livelli
- Categorie PII rilevate
- Formati file supportati
- Modalità di mascheratura
- Installazione
- Eseguibile Windows (EXE standalone)
- Utilizzo — CLI
- Utilizzo — Python API
- Web UI locale (Gradio)
- API REST locale (FastAPI)
- MCP Server stdio
- Span Resolver e mapping consistente
- Gestione metadati
- Audit log JSON
- Report compliance GDPR (PDF)
- Dataset sintetico ed evaluation
- Entity vault per de-anonimizzazione
- Requisiti di sistema
- Limitazioni note
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.0aggressive:background_stay=-3.0,background_to_start=+2.0,span_continuation=+1.5
- Unico layer con categoria nativa
SECRETper 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 |
.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 raccomandato senza OPF (extra [recommended])
pip install "ai-privacy-anonymizer[recommended]"
Installa office, documents, ml (GLiNER), webui, api, rich. Esclude OPF (Layer 1) per evitare il download di ~3 GB a chi non lo serve. È la scelta consigliata per la maggior parte degli utenti.
Setup completo con OPF (extra [full])
pip install "ai-privacy-anonymizer[full]"
Installa l'extra [recommended] e in più OPF dal repository ufficiale (git+https://github.com/openai/privacy-filter). Richiede ~5 GB totali tra dipendenze e modelli.
Comando one-shot (alternativa a [full])
Se hai già installato il pacchetto base, puoi installare tutto con:
privacy-anonymizer --install-full
Esegue internamente pip install "ai-privacy-anonymizer[recommended]" seguito dall'installazione di OPF da git.
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
Solo Layer 1 OPF (installazione esterna, separata)
Se non vuoi [full] e preferisci installare OPF a parte:
pip install git+https://github.com/openai/privacy-filter
Richiede ~3 GB di spazio per il download del modello al primo avvio.
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 comeLaptopvengano 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 | 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ai_privacy_anonymizer-0.2.0.tar.gz.
File metadata
- Download URL: ai_privacy_anonymizer-0.2.0.tar.gz
- Upload date:
- Size: 121.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
440648497c4ad1e4eda997504f04b5b281e427252e78a01bb8871bccd0930ef3
|
|
| MD5 |
0867d5fa1ab8d4e60fe76e10dfb92e64
|
|
| BLAKE2b-256 |
ca7096d32a91a8a628922c3b62199fde1b6b95e41c7d9b2619605ef445f6b515
|
File details
Details for the file ai_privacy_anonymizer-0.2.0-py3-none-any.whl.
File metadata
- Download URL: ai_privacy_anonymizer-0.2.0-py3-none-any.whl
- Upload date:
- Size: 70.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
64fbe5eeece667b5ce91d04fda914884ecf22fad11b12b075dd79b67529dba85
|
|
| MD5 |
1938a47aebcf7e49871e0254b9c00114
|
|
| BLAKE2b-256 |
0c7bfaad9fc4c85623d61f50fb7729228c927755a906ea6c86a699f964af3e34
|