Salta al contenuto principale

Privacy-by-design: dati GDPR e Ollama on-prem

Dove finiscono le copie dei dati personali in un sistema con un modello, cosa chiedono davvero gli articoli del GDPR e come pseudonimizzare e cancellare.

Cosa ne pensi di questo articolo?

Privacy-by-design: dati GDPR e Ollama on-prem
Sovereign AI PMI EUFoto:Tara WinsteadPexels

In Breve

Dove finiscono le copie dei dati personali in un sistema con un modello, cosa chiedono davvero gli articoli del GDPR e come pseudonimizzare e cancellare.

La privacy di un sistema con modelli si decide dove finiscono le copie

Quando un'azienda mette un modello di linguaggio davanti ai propri dati, il rischio per la privacy raramente sta nel modello: sta nelle copie. Il testo della domanda finisce in un registro, i passaggi dei documenti in un indice, la risposta in una cache, tutto in una copia di sicurezza. Ognuna è un trattamento di dati personali con le sue regole, e il principio di «privacy fin dalla progettazione» consiste nel decidere, prima di costruire, quali copie esistano, per quanto e con che protezione. Questo articolo traduce quel principio in scelte e in due programmi, e dice chiaramente dove la norma finisce e dove comincia il lavoro del tuo consulente.

Nessuna parte di questo testo è consulenza legale. Gli articoli del regolamento sono citati dal testo ufficiale, consultato il 2026-11-13; ciò che non ho potuto verificare è dichiarato come tale.

Dove stanno i dati personali in un sistema con un modello

Il primo esercizio è l'inventario. Per ogni punto in cui un testo con dati personali viene scritto, chiediti chi ci può accedere, per quanto tempo resta e se lo puoi cancellare per singola persona.

  • Il prompt: la domanda dell'utente e il contesto che il sistema le aggiunge, spesso con nomi, importi e indirizzi.
  • L'indice dei documenti: i passaggi dei tuoi file, copiati in forma di testo e di vettore in un archivio separato.
  • La cache delle risposte: utile per risparmiare tempo, contiene risposte costruite con quei passaggi.
  • Il registro delle richieste: serve per diagnosticare errori e per rendere conto di cosa è avvenuto, e tende a crescere senza limite.
  • Le copie di sicurezza: contengono tutto il resto, per tutto il periodo di conservazione.

Le prime quattro voci sono sotto il tuo controllo di programmazione, e ciascuna ha una scelta: non scriverla, scriverla in forma ridotta o scriverla con una scadenza. La quinta è sotto il controllo organizzativo, perché una copia si cancella solo quando scade. Un sistema ben fatto parte da qui: il registro, per esempio, può contenere solo l'impronta di una domanda e l'esito, non il testo, e questo decide quanto è grande il problema da gestire in seguito.

Che cosa chiede il regolamento, in poche righe verificate

Il regolamento (UE) 2016/679 ha pochi articoli che pesano su un sistema di questo tipo. L'articolo 5 fissa i principi: i dati devono essere «adeguati, pertinenti e limitati a quanto necessario» (minimizzazione), conservati in forma identificabile non oltre il necessario (limitazione della conservazione) e trattati con adeguata sicurezza. L'articolo 25 chiede misure tecniche e organizzative adeguate, citando la pseudonimizzazione come esempio, e, «per impostazione predefinita», che siano trattati solo i dati necessari per ogni finalità, con riguardo a quantità, portata, periodo di conservazione e accessibilità.

L'articolo 32 chiede un livello di sicurezza adeguato al rischio e cita, fra le misure, la pseudonimizzazione e la cifratura, la capacità di ripristinare la disponibilità dei dati e una procedura per testare le misure. L'articolo 28 riguarda chi tratta dati per tuo conto: ti chiede di scegliere responsabili che offrano garanzie sufficienti e di regolare il rapporto con un contratto o altro atto giuridico, e impone al responsabile di non ricorrere a un altro responsabile senza una tua autorizzazione. Vale per un fornitore di API come per il fornitore del server: chi ospita la macchina su cui girano i dati è un soggetto da inquadrare.

L'articolo 35 richiede una valutazione d'impatto prima di un trattamento che possa presentare un rischio elevato, in particolare con nuove tecnologie. Il paragrafo 3 elenca tre casi in cui è richiesta: la valutazione sistematica di aspetti personali basata su trattamento automatizzato su cui si fondano decisioni con effetti significativi; il trattamento su larga scala di categorie particolari di dati; la sorveglianza sistematica su larga scala di zone pubbliche. Un assistente interno su manuali tecnici probabilmente non rientra; uno che tratta dati sanitari o del personale può rientrarci. La valutazione si fa con il responsabile della protezione dei dati, se c'è.

Pseudonimizzare prima di scrivere

L'articolo 4 definisce la pseudonimizzazione come un trattamento tale che i dati non possano più essere attribuiti a una persona specifica senza informazioni aggiuntive, conservate separatamente e protette. Il considerando 26 aggiunge un punto che sorprende molti: i dati pseudonimizzati restano dati personali, perché potrebbero essere attribuiti a una persona con le informazioni aggiuntive. La pseudonimizzazione riduce il rischio, non cancella l'obbligo: i principi si applicano solo a informazioni anonime in senso proprio, cioè non riferibili a persona identificabile con mezzi ragionevoli.

Il programma seguente applica la pseudonimizzazione a un testo con tre tipi di dati: posta elettronica, IBAN e codice fiscale. Ci sono due dettagli di progetto. Per l'IBAN non basta la forma: il controllo mod 97 della norma ISO 13616 scarta le sequenze che somigliano a un IBAN ma non lo sono, come il codice d'ordine nel testo di prova. Il codice fiscale è controllato solo nella forma, senza verificare la cifra di controllo, e lo dichiaro: un falso positivo qui è accettabile, un falso negativo no. Il gettone è un codice HMAC con chiave: lo stesso valore dà sempre lo stesso gettone, quindi il testo ripulito conserva la possibilità di capire che due menzioni riguardano la stessa persona.

"""Pseudonimizzare un testo prima che raggiunga un log, una cache o un indice.

Dati INVENTATI. La chiave di prova sta nel codice solo per rendere l'esempio
riproducibile: in un sistema vero sta fuori dal repository, in un gestore di segreti.
"""
import hashlib
import hmac
import re

CHIAVE = b"chiave-di-prova-da-non-usare"
EMAIL = re.compile(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}")
IBAN = re.compile(r"\b[A-Z]{2}\d{2}[A-Z0-9]{11,30}\b")
CF = re.compile(r"\b[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]\b")  # SOLO la forma, nessun controllo


def iban_valido(iban):
    """Controllo mod 97 (ISO 13616): sposta le prime 4 cifre in coda,
    sostituisci le lettere con numeri (A=10...Z=35), il resto deve essere 1."""
    s = iban[4:] + iban[:4]
    n = int("".join(str(int(c, 36)) for c in s))
    return n % 97 == 1


def pseudonimo(valore, tipo):
    """Pseudonimo stabile: lo stesso valore da' sempre lo stesso gettone."""
    mac = hmac.new(CHIAVE, valore.encode(), hashlib.sha256).hexdigest()[:8]
    return f"<{tipo}:{mac}>"


def ripulisci(testo):
    def sostituisci(tipo, rx, controllo=None):
        def f(m):
            v = m.group(0)
            if controllo and not controllo(v):
                return v  # sembra un IBAN ma non lo e': non toccare
            return pseudonimo(v, tipo)
        return lambda t: rx.sub(f, t)
    for passo in (sostituisci("EMAIL", EMAIL), sostituisci("IBAN", IBAN, iban_valido), sostituisci("CF", CF)):
        testo = passo(testo)
    return testo


T = ("Il cliente mario.rossi@example.com (CF RSSMRA80A01H501U) chiede di cambiare "
     "l'IBAN in IT60X0542811101000000123456. Risposta a mario.rossi@example.com. "
     "Codice ordine AB12CD34EF567890 non e' un IBAN.")
print(ripulisci(T))

print("\nIBAN di prova valido:", iban_valido("IT60X0542811101000000123456"))
print("stesso IBAN, ultima cifra cambiata:", iban_valido("IT60X0542811101000000123457"))

# Perche' serve la chiave: un hash SENZA chiave di un valore prevedibile si inverte provando.
candidati = ["anna.verdi@example.com", "mario.rossi@example.com", "luca.neri@example.com"]
senza_chiave = hashlib.sha256(b"mario.rossi@example.com").hexdigest()
trovato = [c for c in candidati if hashlib.sha256(c.encode()).hexdigest() == senza_chiave]
print("\nhash senza chiave invertito per tentativi:", trovato)
con_chiave = hmac.new(CHIAVE, b"mario.rossi@example.com", hashlib.sha256).hexdigest()
trovato = [c for c in candidati if hashlib.sha256(c.encode()).hexdigest() == con_chiave]
print("hash con chiave invertito senza la chiave:", trovato)

# Output:
# Il cliente <EMAIL:d30b777e> (CF <CF:b01d087d>) chiede di cambiare l'IBAN in <IBAN:fd15af6b>. Risposta a <EMAIL:d30b777e>. Codice ordine AB12CD34EF567890 non e' un IBAN.
#
# IBAN di prova valido: True
# stesso IBAN, ultima cifra cambiata: False
#
# hash senza chiave invertito per tentativi: ['mario.rossi@example.com']
# hash con chiave invertito senza la chiave: []

L'uscita mostra tre cose. L'indirizzo ripetuto due volte produce lo stesso gettone, quindi la coerenza si conserva; l'IBAN valido viene sostituito, mentre il codice d'ordine, che passa la forma ma non il controllo mod 97, resta intatto. L'ultima parte spiega perché la chiave è indispensabile: un hash senza chiave di un valore prevedibile, come un indirizzo di posta, si inverte provando i candidati, e provare un elenco di indirizzi è un'operazione banale per un computer; con l'HMAC senza la chiave il tentativo fallisce. La chiave deve vivere fuori dal repository e dal sistema che scrive i registri, perché è l'«informazione aggiuntiva» che l'articolo 4 vuole conservata separatamente.

Il limite principale è che espressioni regolari trovano solo dati con una forma. Nomi, indirizzi e riferimenti indiretti («il direttore del reparto che ha fatto causa») richiedono altre tecniche, e nessun programma di poche righe le sostituisce. Il risultato è una misura che riduce l'esposizione per le categorie con struttura, e va accompagnata dalla regola più semplice: non scrivere il testo, se l'impronta basta.

Cancellare davvero: l'originale non basta

L'articolo 17 dà all'interessato il diritto di ottenere la cancellazione senza ingiustificato ritardo in alcuni casi, fra cui quando i dati non sono più necessari per le finalità o l'interessato revoca il consenso, e prevede eccezioni, per esempio per un obbligo legale o per l'accertamento di un diritto in sede giudiziaria. Per un sistema con indice e cache, il punto tecnico è che i dati esistono in più forme derivate: cancellare il file originale lascia i suoi passaggi nell'indice e le risposte che li hanno usati nella cache.

Il programma seguente modella cinque archivi in memoria con dati inventati: documenti, indice, cache, registro di audit e copia di sicurezza. Ogni voce della cache ricorda i soggetti coinvolti, e la cancellazione di un soggetto percorre documenti, indice e cache. Il registro conserva solo impronte, quindi non contiene nulla da cancellare; la copia di sicurezza, invece, resta fino alla scadenza.

"""La cancellazione deve raggiungere i dati DERIVATI, non solo l'originale.

Un sistema di assistenza con documenti, indice di recupero, cache delle risposte,
registro di audit e copia di sicurezza. Dati inventati, strutture in memoria.
"""
import hashlib
from dataclasses import dataclass, field


def impronta(testo):
    return hashlib.sha256(testo.encode()).hexdigest()[:8]


@dataclass
class Sistema:
    documenti: dict = field(default_factory=dict)   # id -> (soggetto, testo)
    indice: dict = field(default_factory=dict)      # id passaggio -> (soggetto, testo)
    cache: dict = field(default_factory=dict)       # impronta domanda -> (id passaggi, risposta, soggetti coinvolti)
    audit: list = field(default_factory=list)       # solo impronte, mai il testo
    copia: dict = field(default_factory=dict)       # istantanea con data di scadenza

    def carica(self, doc_id, soggetto, testo):
        self.documenti[doc_id] = (soggetto, testo)
        self.indice[doc_id + "#0"] = (soggetto, testo)

    def rispondi(self, domanda, passaggi):
        soggetti = {self.indice[p][0] for p in passaggi}
        self.cache[impronta(domanda)] = (tuple(passaggi), "risposta costruita con " + ", ".join(passaggi), soggetti)
        self.audit.append(("domanda", impronta(domanda)))

    def fai_copia(self, giorni):
        self.copia = {"documenti": dict(self.documenti), "scade_tra_giorni": giorni}

    def cancella_soggetto(self, soggetto):
        self.documenti = {k: v for k, v in self.documenti.items() if v[0] != soggetto}
        tolti = {k for k, v in self.indice.items() if v[0] == soggetto}
        self.indice = {k: v for k, v in self.indice.items() if k not in tolti}
        # le risposte in cache costruite con quei passaggi devono sparire
        self.cache = {k: v for k, v in self.cache.items() if not (set(v[0]) & tolti)}
        return len(tolti)

    def tracce(self, soggetto):
        return {
            "documenti": sum(1 for v in self.documenti.values() if v[0] == soggetto),
            "indice": sum(1 for v in self.indice.values() if v[0] == soggetto),
            "cache": sum(1 for v in self.cache.values() if soggetto in v[2]),
            "copia": sum(1 for v in self.copia.get("documenti", {}).values() if v[0] == soggetto),
        }


s = Sistema()
s.carica("D1", "cliente-17", "Contratto di Mario Rossi, indirizzo e IBAN")
s.carica("D2", "cliente-18", "Contratto di Anna Verdi")
s.rispondi("Qual e' l'IBAN del cliente 17?", ["D1#0"])
s.rispondi("Che cosa prevede il contratto 18?", ["D2#0"])
s.fai_copia(giorni=30)
print("prima   :", s.tracce("cliente-17"))
print("tolti dall'indice:", s.cancella_soggetto("cliente-17"))
print("dopo    :", s.tracce("cliente-17"))
print("registro di audit (solo impronte):", s.audit)
print("la copia scade fra", s.copia["scade_tra_giorni"], "giorni")

# Output:
# prima   : {'documenti': 1, 'indice': 1, 'cache': 1, 'copia': 1}
# tolti dall'indice: 1
# dopo    : {'documenti': 0, 'indice': 0, 'cache': 0, 'copia': 1}
# registro di audit (solo impronte): [('domanda', '0ee6890f'), ('domanda', 'a1289d9c')]
# la copia scade fra 30 giorni

Prima della cancellazione il soggetto compare in tutti e quattro gli archivi; dopo, resta solo nella copia di sicurezza, che scade fra 30 giorni. La domanda se una persona possa essere cancellata subito dalle copie, o se basti non ripristinarle e lasciarle scadere, ha una risposta che dipende dal caso e dalle indicazioni dell'autorità di controllo, che qui non ho verificato: va decisa con il tuo consulente e scritta nella procedura. Due punti restano dalla parte del progetto: il registro che non contiene testo ha già risolto il suo pezzo, e una cache con una scadenza breve riduce il numero di voci da cercare. L'articolo sul backup e ripristino tratta la parte delle copie, e quello sul registro di audit quella del registro.

Dove sono i dati: residenza e trasferimenti

Tenere i dati su un server in Europa semplifica un punto, il trasferimento verso un paese terzo, ma non li mette al riparo da tutto. Il capo V del regolamento si occupa dei trasferimenti: l'articolo 44 stabilisce che un trasferimento ha luogo soltanto se titolare e responsabile rispettano le condizioni del capo; l'articolo 45 lo ammette se la Commissione ha deciso che il paese terzo garantisce un livello di protezione adeguato; l'articolo 46, in mancanza di una decisione, lo ammette con garanzie adeguate, fra cui le clausole tipo di protezione dei dati adottate dalla Commissione. Se mandi dati a un fornitore stabilito fuori dall'Unione, o a un fornitore europeo che si serve di un responsabile o di personale in un paese terzo, il capo V entra in gioco.

Per gli Stati Uniti esiste una decisione di adeguatezza: la decisione di esecuzione (UE) 2023/1795 della Commissione del 10 luglio 2023, il cui articolo 1 stabilisce che gli Stati Uniti garantiscono un livello adeguato di protezione per i dati trasferiti verso organizzazioni incluse nell'elenco del Data Privacy Framework tenuto dal Dipartimento del Commercio. Il Tribunale dell'Unione, con sentenza del 3 settembre 2025 nella causa T-553/23 (Latombe contro Commissione), ha respinto il ricorso contro quella decisione. Contro la sentenza è stata proposta impugnazione il 31 ottobre 2025 (causa C-703/25 P, avviso pubblicato nella Gazzetta ufficiale il 22 dicembre 2025). Non ho verificato l'esito dell'impugnazione né eventuali sviluppi successivi: prima di affidarti alla decisione, controlla lo stato attuale. Un'azienda prudente tiene pronta un'alternativa documentata, per esempio le clausole tipo.

C'è poi un secondo aspetto che la residenza non risolve: chi può obbligare il fornitore. La norma statunitense 18 U.S.C. § 2713 dice che un fornitore di servizi di comunicazione elettronica o di calcolo remoto deve adempiere agli obblighi del capitolo di conservare, copiare o comunicare il contenuto di comunicazioni e i dati relativi a un cliente che si trovino nella sua disponibilità, possesso o controllo, «indipendentemente dal fatto che tali dati si trovino all'interno o all'esterno degli Stati Uniti». Che cosa significhi per un fornitore europeo dipende dai suoi legami societari con soggetti soggetti alla giurisdizione statunitense, e non ho una fonte che lo stabilisca per nessun fornitore in particolare. La domanda pratica è questa: chi possiede e controlla l'azienda che ospita i miei dati? La risposta la trovi nel contratto e nel registro delle imprese, non in un'etichetta «cloud europeo». Il tema ritorna nell'articolo sui casi in cui il cloud europeo conviene.

Se addestri o rifinisci un modello con dati personali, la questione cambia natura, perché i dati possono lasciare tracce nei pesi. Il Comitato europeo per la protezione dei dati ha pubblicato il 18 dicembre 2024 il parere 28/2024 su alcuni aspetti della protezione dei dati nei modelli di intelligenza artificiale; non ne riporto le conclusioni perché non ne ho letto il testo, e ti rimando al documento. In questa serie ci limitiamo al caso più comune, il recupero di documenti con un modello che non cambia, descritto nell'articolo sul recupero ibrido.

Che cosa l'hosting in proprio non risolve

Tenere il server in casa non toglie nessuno degli obblighi sopra: ti assegna tutti i compiti di sicurezza dell'articolo 32, dalla cifratura al ripristino, che un fornitore gestito fa al posto tuo. L'articolo sul server in produzione mostra che una porta lasciata aperta è il rischio più concreto, e quello sui costi che il lavoro di amministrazione pesa più del canone. Il trade-off è onesto: guadagni il controllo su dove sono i dati e chi li vede, e ti prendi la responsabilità di tutto il resto.

Domande frequenti

Un server in Europa basta per essere conformi al GDPR?

No. La residenza riguarda il capo V sui trasferimenti; restano la base giuridica, la minimizzazione, la conservazione, la sicurezza e i contratti con chi tratta dati per te. Anche un fornitore europeo può avere legami con soggetti extra-europei, da verificare.

Un dato pseudonimizzato è un dato anonimo?

No. Secondo il considerando 26 i dati pseudonimizzati, attribuibili a una persona con informazioni aggiuntive, restano informazioni su una persona identificabile. La pseudonimizzazione è una misura di sicurezza, non un'uscita dal regolamento.

Serve una valutazione d'impatto per un assistente interno?

Dipende dal trattamento. L'articolo 35 la richiede in caso di rischio elevato e ne elenca tre casi, fra cui il trattamento su larga scala di categorie particolari di dati. Per un assistente su dati sanitari o del personale conviene valutarla con il responsabile della protezione dei dati.

Che cosa sai fare adesso

Sai inventariare i punti in cui un sistema con un modello copia dati personali e decidere, per ciascuno, se scrivere, ridurre o far scadere. Sai citare gli articoli del regolamento che pesano, sai che la pseudonimizzazione non rende anonimo un dato e sai perché serve una chiave. Sai che cancellare un soggetto significa percorrere indice e cache, non solo l'originale, e sai quali domande di fondo restano da porre a un fornitore, fra cui chi lo controlla. Per le decisioni di merito, in particolare sui trasferimenti, serve il tuo consulente: qui hai gli strumenti per fargli le domande giuste.

Nella stessa serie trovi anche il confronto fra le famiglie di modelli, come misurare la qualità di un RAG, il dimensionamento per più utenti e il riepilogo con la scheda di decisione.

Fonti

I due programmi sono stati eseguiti con Python 3.14.4 (python3 -I), due volte, con uscita identica; i dati sono inventati e le strutture sono in memoria, senza database né rete. Nessun giudizio legale è espresso: le norme sono riportate dal testo, e i punti non verificati sono dichiarati nel testo.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti