Copyright (C) 2026 SummerSeaSun
Un layer locale per utilizzare modelli di intelligenza artificiale riducendo l'esposizione di dati sensibili.
L'idea è semplice:
Un LLM è uno strumento, non un luogo dove depositare dati aziendali.
Prima che un contenuto venga inviato al modello, questo progetto identifica e sostituisce informazioni personali o riservate con identificatori anonimi.
Il modello lavora sul contenuto protetto. I dati originali rimangono sotto il controllo dell'utente (interamente eseguita in locale).
L'intelligenza artificiale non elimina la necessità di sicurezza. La rende ancora più importante.
Usare l'AI per proteggere i dati che vengono forniti all'AI è parte del corretto utilizzo dello strumento.
Un modello deve elaborare informazioni. Non deve diventare il punto in cui le informazioni sensibili vengono disperse.
Questo progetto è in fase iniziale. L'obiettivo è sperimentare un approccio locale alla pseudonimizzazione dei dati prima dell'utilizzo con modelli LLM.
Non è ancora considerato un sistema completo di protezione dei dati.
input/*.{txt,log,csv}
│
▼
┌──────────┐
│ Regex │ entità strutturate (email, URL, IP, IBAN, CF, P.IVA, CAP)
└────┬─────┘
│
▼
┌───────────┐
│ GLiNER2 │ entità semantiche (persona, azienda, data, ...)
└─────┬─────┘
│
▼
output/*_anon.{txt,log,csv}
| Modulo | Ruolo |
|---|---|---|
| config.py | Percorsi, modello, labels |
| regex_rules.py | Pattern regex per dati strutturati |
| anonymizer.py | Sostituzione con ID progressivi |
| gliner_utils.py | Riconoscimento entità via GLiNER2 (BERT) con chunking (fallback regex se non disponibile) |
| main.py | Orchestratore della pipeline |
La classe Anonymizer gestisce la sostituzione delle entità con placeholder progressivi:
EMAIL_001,EMAIL_002, …PERSON_001,PERSON_002, …COMPANY_001,COMPANY_002, …DATE_001,DATE_002, …URL_001,URL_002, …PHONE_001,PHONE_002, …IP_001,IP_002, …IBAN_001,IBAN_002, …
Lo stesso valore riceve sempre lo stesso ID (coerenza intra-documento). Il mapping è reinizializzato ad ogni lancio.
Per ripristinare il testo originale da un file anonimizzato (usando il mapping salvato), esiste restore_text:
Ogni regola è una voce nel dizionario REGEX_RULES in regex_rules.py.
La chiave (EMAIL, IP, …) diventa il prefisso del placeholder (EMAIL_001).
Esempio — la regex per il codice fiscale:
REGEX_RULES = {
...
"CF": r"\b[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]\b",
}Analisi del pattern pezzo per pezzo:
| Pezzo | Significato |
|---|---|
\b |
confine di parola (evita falsi positivi dentro parole più lunghe) |
[A-Z]{6} |
6 lettere maiuscole (cognome + nome) |
\d{2} |
2 cifre (anno di nascita) |
[A-Z] |
1 lettera (mese) |
\d{2} |
2 cifre (giorno / sesso) |
[A-Z] |
1 lettera (codice comune) |
\d{3} |
3 cifre (progressivo) |
[A-Z] |
1 lettera (carattere di controllo) |
\b |
confine di parola |
Aggiungere una nuova regex basta una riga:
REGEX_RULES = {
...
"CODICE_FATTURA": r"\bFATT-\d{5}\b",
}Il motore cerca automaticamente tutti i match e li sostituisce con CODICE_FATTURA_001, CODICE_FATTURA_002, …
make test39 test che coprono:
| File | Cosa testa |
|---|---|
test_anonymizer.py |
create_id (sequenziale, idempotente, contatori separati per tipo), anonymize_text (sostituzione, ordine inverso), persistenza su file |
test_regex_rules.py |
Ogni pattern regex (match giusti, falsi positivi, edge case) |
test_gliner_utils.py |
Fallback regex quando GLiNER2 non è installato (email, telefono, IP, IBAN, label sconosciuta) |
test_main.py |
find_regex_entities, merge_entities (deduplica, ordinamento), process_file integrato |
- Python 3.10+
pipenv
make buildScarica il modello (205M, BERT-based) per il riconoscimento di persona, azienda, data, ecc.:
pipenv run python -c "from gliner2 import GLiNER2; GLiNER2.from_pretrained('fastino/gliner2-base-v1')"Senza il modello, GLiNER2 usa un fallback regex per le sole entità strutturate (email, telefono, IP, IBAN).
Per usare un percorso locale (es. per ambiente offline), imposta in config.py:
GLINER_MODEL = "./models/gliner2-base-v1"
HF_OFFLINE = Truee scarica il modello manualmente:
pipenv run python -c "from gliner2 import GLiNER2; GLiNER2.from_pretrained('fastino/gliner2-base-v1').save_pretrained('./models/gliner2-base-v1')"mkdir -p input output
echo "Mario Rossi — mario@example.com" > input/test.txt
pipenv run python main.py input/test.txt
cat output/test_anon.txtOppure via Make:
make INPUT=input/test.txt appPer ripristinare un file anonimizzato usando il mapping generato:
pipenv run python main.py --restore output/test_anon.txt
cat output/test_anon_restored.txtOppure via Make:
make restore INPUT=output/test_anon.txt