Skip to content

SummerSeaSun/PuliAI

Repository files navigation

openspec — PuliAI

Pulisci dati sensibili prima dell'AI

License: GPL v3

PuliAI demo

Copyright (C) 2026 SummerSeaSun

LLM Privacy Gateway

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).


Perché

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.


Status: experimental

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.

Architettura

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}

Componenti

| 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 |

Anonymizer

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:

Aggiungere una regex

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, …

Test

make test

39 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

Requisiti

  • Python 3.10+
  • pipenv
make build

Modello GLiNER2

Scarica 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 = True

e 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')"

Utilizzo

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.txt

Oppure via Make:

make INPUT=input/test.txt app

Ripristino

Per ripristinare un file anonimizzato usando il mapping generato:

pipenv run python main.py --restore output/test_anon.txt
cat output/test_anon_restored.txt

Oppure via Make:

make restore INPUT=output/test_anon.txt

About

A local privacy layer that detects and pseudonymizes sensitive data before sending prompts to LLMs.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages