Osservatorio sulla Sovranità Digitale della Posta Elettronica della Pubblica Amministrazione Italiana
Come abbiamo determinato chi gestisce la posta di ciascun ente pubblico italiano (PEC esclusa).
is_legit_email_domain. Quando una regola non si attiva, l'ente resta unknown —
mai assegnato a un MX "plausibile ma non provato".
Ogni ente attraversa una catena di tentativi. Il primo che produce un MX accettato dal validatore vince:
fetch_indicepa.py): si parte dal campo Sito_istituzionale di IndicePA, con sei tier di correzione automatica (override manuale, enrichment LLM curato, enrichment PEC-only Wikidata, AOO/UO Tier-6).preprocess.py): risoluzione DNS (MX/SPF/DKIM/CNAME/ASN/tenant) sul dominio risultante.recover_it_unknowns.py): per ogni ente ancora unknown con domain_fallbacks (mail non-PEC del record IndicePA), si tentano i fallback uno a uno, gated da is_legit_email_domain.postprocess.py): banner SMTP + scraping del sito (gated).finalize_it_unknowns.py): cinque strategie sugli ultimi unknowns — Tier-6 AOO/UO, PEC pubblica, Wikidata P856, scraping homepage, ricerca DuckDuckGo + scraping (tutte gated).Ogni stage scrive il campo mx_discovery_method con la tag canonica del metodo applicato. Il tag viene mostrato nel popup dell'ente con tooltip esplicativo e link a questa pagina.
is_legit_email_domainNucleo della pipeline. Per ogni dominio candidato proveniente da scraping, fallback IndicePA o ricerca web, decide se è legittimamente associato all'ente. Default: REJECT. Accetta solo se scatta una di queste regole esplicite:
garr.it, sogei.it, consorzi ASMEL); accettato per qualsiasi ente.lepida.it = Emilia-Romagna, regione.vda.it = Valle d'Aosta, ruparpiemonte.it = Piemonte, ecc.). Accettato solo se: (a) l'ente è una PA locale (presenza di marker comune., provincia., codice di provincia, ecc.); (b) e l'ente è geograficamente in quella regione, verificato tramite mappatura province (110 codici) + capoluoghi (107 nomi). Rigetto automatico per ministeri/PA centrali (dominio *.gov.it).comune., mail., www., ecc.), le label residue dei due domini condividono almeno un elemento (≥3 caratteri).consorfarm.it ↔ consofarm.it, consorziolagodibracciano.it ↔ consorziolagodibraciano.it, hyphenation come aslroma1.it ↔ asl-roma1.it. La soglia DL=1 + min-length 6 esclude false positive su parole brevi (es. roma/noma).{città}.aci.it ma il sito reale è aci{città}.it: ad esempio aciarezzo.it ↔ arezzo.aci.it dove aciarezzo = arezzo + aci (copertura 100%). Recupera ~16 enti del cluster ACI provinciali + ordini professionali.legalmail.it, arubapec.it, postecert.it, ecc.) sono sempre rigettati come base di classificazione MX (per design: la PEC non è considerata posta "operativa" per scopo di sovranità).Codice: src/mail_sovereignty/scrape_validator.py. Test: scripts/_test_scrape_validator.py.
Ogni voce mostra: la tag stabile (citabile come anchor di questa pagina), una descrizione estesa, la regola formale che la attiva, le sorgenti di evidenza, e il comportamento in caso di fallimento.
Il dominio dichiarato come Sito_istituzionale nel record IndicePA dell'ente ha record MX validi. Nessuna correzione, nessun recupero. È il caso ideale e il più frequente per i Comuni con presenza digitale matura.
lookup_mx(seed.domain) restituisce almeno un MX.comune.milano.it → p-milano-mx-001.it-milano.local (su infrastruttura proprietaria).L'ente non ha dominio in IndicePA. Il sistema genera candidati dal nome (rimozione diacritici, prefissi comune-/provincia-, TLD nazionale .it) e accetta il primo con MX risolvibile.
preprocess.guess_domains() + verifica MX.Dominio corretto a mano dai mantenitori del dataset. Usato per fixare typo IndicePA (castefranco → castelfranco), domini *.gov.it defunti migrati a comune.{nome}.{prov}.it, o casi che la pipeline automatica non riesce a recuperare correttamente.
IT_MANUAL_DOMAIN_OVERRIDES (dict codice_ipa → dominio) in scripts/fetch_indicepa.py. Ogni voce ha un commento di giustificazione.Numero limitato (≪100). Crescita lenta, una voce per bug confermato.
Per gli enti che IndicePA segnala con sola PEC e dove né Wikidata né Wikipedia hanno informazioni, generiamo un prompt strutturato (lista di codici IPA + nomi ufficiali) e lo sottomettiamo a una sessione LLM. Le risposte vengono validate a mano e committate in data/manual_llm_enrichment.json.
data/manual_llm_enrichment.json + verifica sintattica del hostname + MX risolvibile a runtime.Generazione non riproducibile (LLM-mediata), consumo deterministico. Vedi scripts/generate_llm_enrichment_prompt.py.
Quando l'ente IndicePA espone solo indirizzi PEC, lo script enrich_pec_only.py tenta automaticamente di recuperare il dominio istituzionale tramite proprietà P856 di Wikidata indicizzata sul codice ISTAT o sul nome, con fallback al titolo Wikipedia. La verifica MX è obbligatoria prima di accettare il dominio.
IndicePA pubblica oltre al dataset enti anche due dataset ausiliari: Aree Organizzative Omogenee (AOO) e Unità Organizzative (UO). Questi record contengono email non-PEC dei responsabili (mail_resp, mail1..3 con tipo_mail* ≠ Pec). Per ministeri e PA centrali è spesso l'unica fonte di domini reali (il record principale ha solo PEC).
is_legit_email_domain(dominio, seed.domain, codice_ipa=...); vengono accettati solo i superstiti. I rigetti vengono loggati per audit (vedi data/indicepa_extended_emails.json → filtered_out).m_it (Min. Interno): 283 record AOO. Domini scoperti: interno.it ✓, vigilfuoco.it ✗ (rigettato — ente separato), regione.vda.it ✗ (rigettato — piattaforma regionale fuori scope per un ministero nazionale).Il record enti stesso espone fino a 5 campi Mail*. Il fetch ne estrae i domini non-PEC distinti dal primario e li salva in seed.domain_fallbacks. Se il dominio primario non risolve, recover_it_unknowns.py prova ciascun fallback in ordine.
fb, is_legit_email_domain(fb, seed.domain, codice_ipa=...) deve restituire True. Solo allora si esegue la classificazione DNS. Rigetti loggati in data/reports/recover_it_unknowns_rejections.json.istruzione.it per scuole — gestito tramite la regola specifica istruzione_miur_tenant (sotto).Le scuole statali italiane (categoria IndicePA L33) non hanno un proprio tenant Microsoft 365: la posta dei dirigenti scolastici, segreterie e docenti abilitati è ospitata sul tenant centrale del Ministero dell'Istruzione e del Merito (MIM) all'indirizzo istruzione.it. Questa dipendenza è una realtà istituzionale, non una misattribuzione — ma è importante segnalarla distintamente da una scuola che gestisce un proprio tenant.
seed.ipa_codice_categoria == "L33" (nessuna euristica su codice_ipa).istruzione.it.istruzione.it deve contenere miuristruzione.onmicrosoft.com (prova crittografica del tenant MIM).miur_tenant_unverified nel report, ente resta unknown. Nessuna assunzione "questo sembra una scuola, sarà MIM".iccaroberlingieri.edu.it (I.C. Caro-Berlingieri): categoria L33 ✓, fallback istruzione.it ✓, DKIM selector1-istruzione-it._domainkey.miuristruzione.onmicrosoft.com ✓ → tag istruzione_miur_tenant.Per i comuni che fallirebbero tutti i tentativi precedenti, una query SPARQL batch su Wikidata recupera la proprietà P856 (sito ufficiale) indicizzata sul codice ISTAT 6-cifre del comune. Cattura tipici typo IndicePA e migrazioni *.gov.it → comune.{nome}.{prov}.it.
SELECT ?web WHERE { ?city wdt:P3829 "{istat6}"; wdt:P856 ?web } + verifica MX. Solo se il dominio Wikidata differisce dal primario IndicePA viene considerato (idempotenza).Alcuni enti utilizzano come PEC ufficiale infrastrutture pubblicamente operate (non provider commerciali): cert.ruparpiemonte.it (CSI Piemonte, ICT regionale sovrano), asmepec.it (consorzio comuni ASMEL). Quando il record IndicePA mostra solo PEC di questo tipo, classifichiamo l'ente come regional-public — anche senza un MX proprio risolto.
Quando i metodi precedenti falliscono, scarichiamo l'homepage (e alcune sotto-pagine: /contatti, /amministrazione-trasparente, ecc.) ed estraiamo gli indirizzi email visibili (incluso il decifrato dei mailto TYPO3 cifrati con Caesar). Ogni dominio estratto passa per il validatore.
is_legit_email_domain(email_host, ente_domain) deve restituire True. Se rigettata, la coppia (host, ragione) viene loggata in data/reports/cleanup_invalid_mx_attributions.json.Ultima risorsa per enti con dominio primario defunto e nessuna voce Wikidata. Query DuckDuckGo HTML (no JS, no rate limit aggressivo) per il nome dell'ente; candidati URL filtrati per somiglianza al nome e dominio plausibile; scraping dell'homepage del candidato; validazione is_legit del dominio mail estratto.
homepage_scrape (validatore is_legit obbligatorio).Per gli enti già classificati come independent (MX proprio, nessun match con i grandi provider), apriamo una connessione SMTP al primo MX e leggiamo il banner EHLO. Stringhe come "Postfix (Ubuntu)", "Exchange 2019", "Plesk SMTP server" arricchiscono il record con il software identificato.
SMTP_BANNER_KEYWORDS in constants.py. Concorrenza limitata (5) per non sembrare scanner.Nessuno dei 13 metodi sopra ha prodotto un MX accettabile. L'ente resta unknown e non viene assegnato a un provider. Le motivazioni di rigetto del validatore sono comunque archiviate per consentire revisione manuale futura.
unknown è una metrica di onestà del dataset, non un fallimento.Caricamento statistiche…
Aggiornato automaticamente a ogni build del frontend (vedi scripts/build_frontend.py).
Un ente classificato come unknown può esserlo per varie ragioni — tutte riconducibili al principio "meglio onesto che fuorviante":
Le regole 6.5 (fuzzy Damerau-Levenshtein ≤ 1) e 6.6 (label concatenation) sono già attive — catturano typo singoli e pattern del tipo aciarezzo.it ↔ arezzo.aci.it. Restano unknown i casi che richiedono override manuale (mismatch semantici non risolvibili automaticamente senza assunzioni rischiose).
Ogni ente porta un punteggio di affidabilità della classificazione (0–100%): quanto è solida l'evidenza DNS che sostiene il provider attribuito. Il modello è un port fedele del classificatore ESORICS 2026 di David Huser e colleghi (citazione completa nei Riferimenti), adattato alla nostra architettura: da noi il provider è già deciso per keyword sui record DNS, qui calcoliamo quanto fidarsene e la giurisdizione dell'infrastruttura di posta.
Il punteggio nasce da un set di 7 regole: si scorre dall'evidenza più forte alla più debole e si prende la prima che combacia. Solo i tre segnali di routing/autorizzazione (MX, SPF, DKIM) determinano la regola di base; gli altri (tenant MS365, autodiscover) contribuiscono solo come boost (+0,02 ciascuno, cap a 1,0). Razionale upstream: avere un tenant Microsoft 365 (es. per Teams) non prova che la posta sia ospitata lì.
| regola | segnali richiesti | gateway | base |
|---|---|---|---|
mx_spf | MX + SPF | — | 0,90 |
mx_only | MX | — | 0,80 |
spf_gw | SPF | sì | 0,70 |
dkim_gw | DKIM | sì | 0,65 |
dkim_spf | DKIM + SPF | — | 0,60 |
spf_only | SPF | — | 0,50 |
fallback | — (catch-all) | — | 0,40 |
Per gli enti senza un backend cloud riconosciuto (posta self-hosted o provider minore) non basta dire "indipendente": li qualifichiamo per giurisdizione dell'IP del server MX (paese dell'ASN via Team Cymru). La confidenza usa tabelle dedicate a base piatta (nessun boost, perché i segnali cloud sono irrilevanti alla classificazione per paese):
| caso | 🇮🇹 domestico (IT) | 🌍 estero |
|---|---|---|
| MX + SPF | dom_mx_spf 0,80 | frgn_mx_spf 0,60 |
| solo MX | dom_mx_only 0,70 | frgn_mx_only 0,50 |
| solo evidenza secondaria | dom_secondary 0,20 | frgn_secondary 0,10 |
| niente | dom_none 0,00 | frgn_none 0,00 |
L'etichetta di sovranità nel popup (🇮🇹 MX sovrano / 🌍 MX estero / misto) deriva da mx_countries: domestic se tutti gli MX sono in IT, foreign se nessuno, mixed se alcuni.
Caso insidioso: un ente risulta microsoft/google per via del tenant o del DKIM, ma il suo record MX punta a un server self-hosted in Italia (non a *.protection.outlook.com). Significa che il cloud serve Teams/SharePoint, mentre la posta in entrata resta sovrana. In questi casi riclassifichiamo l'ente per giurisdizione invece che come cloud estero.
La confidenza è anche una mappa di dove verificare. Gli enti a confidenza bassa (< 0,60) pur essendo classificati sono i candidati prioritari per la futura validazione attiva via bounce-probing (invio a indirizzo inesistente + analisi del messaggio di ritorno NDR, che rivela il MTA reale del backend). Il report completo — distribuzione aggregata, confidenza per provider, regole attivate, sovranità e lista dei candidati bounce — è rigenerato a ogni run:
src/mail_sovereignty/classification_confidence.py (port + test di fedeltà), scripts/compute_confidence.py, scripts/report_confidence.py.Questo osservatorio è un fork del lavoro di David Huser e colleghi, a cui va il credito per il metodo di classificazione e per il modello di confidence qui adottato.
provider_classification.Osservatorio Sovranità Digitale PA — codice e dati su GitHub · Licenza dati: ODbL-1.0 + CC-BY-4.0 · Codice: MIT · Metodo: mxmap/ESORICS 2026