Il 9 febbraio 2026 una singola firma di rilevamento dentro Microsoft Defender for Office e’ passata da circa 21.000 messaggi al giorno a oltre 1,3 milioni. Due giorni dopo ha toccato 2,3 milioni. La firma era nata per cercare prompt injection nascoste nelle email destinate agli assistenti AI: quello che ha trovato erano campagne di phishing finanziario che usavano la stessa tecnica per un motivo completamente diverso, cioe’ spezzare le parole chiave sotto il naso dei filtri antispam. Microsoft ha pubblicato la ricostruzione il 3 settembre 2026.
La tecnica si chiama ASCII smuggling e non richiede nessuna vulnerabilita’. Usa 128 caratteri Unicode che esistono, sono validi, e non si vedono. Se hai un form di contatto che finisce in un CRM, una mailbox letta da un workflow n8n, o un assistente che riassume i ticket, quei caratteri attraversano tutta la catena senza che nessun livello li tocchi.
I 128 caratteri che nessuno guarda
Lo standard Unicode definisce oggi circa 150.000 caratteri e ha spazio per oltre un milione. Dentro questo repertorio c’e’ un blocco di 128 code point, da U+E0000 a U+E007F, chiamato blocco Tags. Ricalca quasi perfettamente la tabella ASCII: U+E0041 corrisponde ad “A”, U+E0061 ad “a”, U+E0020 allo spazio. La differenza e’ una sola e sta nel progetto originale: sono invisibili per costruzione.
Il blocco e’ stato deprecato due volte. Nella prima versione dello standard doveva servire a marcare la lingua di un testo con etichette tipo “en” o “jp”; il piano e’ stato abbandonato. Poi si e’ pensato di riusarlo per i codici paese, da appendere a una bandiera generica per trasformarla nella bandiera nazionale corretta; anche quel piano e’ caduto. Il blocco e’ rimasto nello standard, senza un uso, e quindi senza nessuno che lo controlli. La scoperta che quei caratteri restano leggibili dagli LLM pur essendo invisibili nelle interfacce viene attribuita a Riley Goodside, prompt engineer di Scale AI.
Il residuo di quel secondo tentativo e’ ancora vivo e conta, come vedremo, nel momento in cui decidi di ripulire: le bandiere delle suddivisioni nazionali usano davvero il blocco Tags. La bandiera scozzese e’ una bandiera nera seguita da sei caratteri di quel blocco, e se li rimuovi tutti resta una bandiera nera.
Cosa e’ successo tra febbraio e maggio 2026
Il dettaglio interessante della telemetria Microsoft non e’ il picco, e’ la forma. L’8 febbraio la firma scattava su circa 21.000 messaggi. Il 9 febbraio su oltre 1,3 milioni: due ordini di grandezza in un giorno. Il picco arriva l’11 febbraio sopra i 2,3 milioni, crolla domenica 15 e riparte il lunedi’. Il flusso e’ andato avanti per mesi ed e’ calato bruscamente a meta’ maggio.
Dietro c’erano circa 150 domini mittente a tema finanziario, usa e getta, che valevano il 96% circa del volume intercettato. Il 9 febbraio erano 148, e i primi venti per volume sono costruiti ricombinando 28 sole parole: advance, boost, business, capital, funding, guardian, loc e simili. Nomi tipo guardiangrowthfunding o thebusinessloanexpress, 20.000-30.000 messaggi ciascuno in un giorno solo.
Il contenuto era phishing finanziario ordinario: prestiti alle imprese, linee di credito, anticipi. Quando Microsoft ha aperto i messaggi cercando le istruzioni nascoste per un modello, non ne ha trovate. I caratteri invisibili erano infilati dentro le parole chiave. La parola funding viaggiava come fun + U+E0020 + ding.
Per chi legge l’email, e per ogni pipeline che normalizza prima di analizzare, la parola resta “funding”. Per una regex che cerca la stringa letterale, quella sequenza di byte non contiene piu’ la parola. Ma il bersaglio grosso, dice Microsoft, non e’ la regex: sono i classificatori ML e NLP che oggi fanno la maggior parte del lavoro antispam. Un classificatore non ragiona su parole intere come le vedi tu, spezza il testo in token e sotto-token. Inserisci un carattere invisibile a meta’ e il tokenizzatore non riconosce piu’ l’unita’ familiare: puo’ produrre fun, un carattere inatteso e ding, oppure token rari o sconosciuti. A meno che la normalizzazione non giri prima, e in quel caso il carattere sparisce e resta “funding”.
Tutta la partita si gioca su quel “a meno che”. Nel caso di Defender il buco non si e’ aperto: oltre il 99% dei messaggi e’ stato bloccato da livelli che non dipendono dal riconoscere i caratteri invisibili, cioe’ reputazione di mittente, IP, URL e dominio, classificazione ML, rilevamento di brand impersonation, controlli di autenticazione. Microsoft dichiara anche di estrarre il testo visibile via OCR su un’immagine del messaggio, che e’ il modo piu’ brutale e piu’ definitivo di vedere quello che vede l’utente.
Perche’ questo non e’ un problema del tuo gateway di posta
Qui arriva la parte che riguarda una PMI, ed e’ l’opposto di quello che sembra. Se usi Microsoft 365 o Google Workspace, il livello che questa campagna cercava di aggirare e’ quello meglio presidiato che hai. Non e’ li’ la tua esposizione.
La tua esposizione e’ tutto cio’ che hai costruito negli ultimi diciotto mesi a valle di quel gateway, dove non c’e’ nessun vendor che normalizza per te. Tre superfici concrete:
Il form di contatto WordPress. Contact Form 7, Gravity Forms, WPForms accettano qualunque testo UTF-8 valido. La funzione con cui WordPress ripulisce i campi testo, sanitize_text_field(), toglie i tag HTML, comprime gli spazi e rimuove le sequenze percent-encoded. Il codice sorgente e’ pubblico e si legge in trenta secondi: non c’e’ una riga che tocchi il blocco Tags. Quel testo finisce nel database, nell’email di notifica, nel CRM e, sempre piu’ spesso, in un prompt.
Il nodo che legge la mailbox. Un workflow n8n o Make che prende le email in arrivo e le passa a un modello per classificarle o riassumerle lavora sul corpo grezzo. Nessuno di quei nodi normalizza Unicode di default. Qui il rischio torna a essere quello originale, non l’evasione dei filtri ma l’istruzione nascosta, e vale la pena essere precisi su cosa puo’ e non puo’ succedere: un LLM che legge non esegue nulla da solo. Il danno arriva quando il modello ha strumenti collegati, cioe’ quando puo’ rispondere, inoltrare, scrivere su un record o chiamare una API. E’ la stessa distinzione tra prevenzione e contenimento di cui ho scritto a proposito dei permessi degli agenti AI: il carattere invisibile e’ il vettore, il perimetro dei permessi decide se diventa un incidente.
La pipeline editoriale. Testo generato da un modello, copiato dentro l’editor WordPress e pubblicato. Alcuni output di LLM contengono caratteri zero-width e marcatori invisibili; finiscono nell’HTML servito e da li’ dentro l’indice di ricerca. Non e’ un attacco, e’ sporcizia, ma e’ la stessa classe di problema: nessuno guarda quei byte perche’ nessuno li vede.
Il test che fai in due minuti
Prima di scrivere un filtro, misura. Serve sapere se nei tuoi dati questi caratteri ci sono gia’ e in che quantita’. Questo script conta i code point del blocco Tags e i principali zero-width in un file di testo, un export CSV o un incolla da terminale:
import re, sys
TAGS = re.compile(r"[\U000E0000-\U000E007F]")
ZERO = re.compile(r"[\u200b-\u200f\u2060-\u2064\ufeff]")
testo = sys.stdin.read()
tag = TAGS.findall(testo)
zw = ZERO.findall(testo)
print("blocco Tags:", len(tag), sorted({hex(ord(c)) for c in tag}))
print("zero-width :", len(zw), sorted({hex(ord(c)) for c in zw}))
print("ripulito :", ZERO.sub("", TAGS.sub("", testo))[:200])
Provalo prima su un caso costruito, cosi’ sai che funziona: la stringa "fun" + "\U000E0020" + "ding" e’ lunga 8 caratteri in Python e 11 byte in UTF-8, si stampa come “funding” e dopo il filtro torna a essere 7 caratteri. Poi passaci sopra l’export degli ultimi trecento messaggi del form di contatto. Se il conteggio e’ zero su tutto, hai finito e ti sei tolto un dubbio. Se non e’ zero, sai esattamente dove entrano.
Il filtro per WordPress, con la sequenza giusta
In PHP il blocco si intercetta con una classe di caratteri e il modificatore u, che attiva il supporto Unicode di PCRE. La versione minima, verificata, e’:
preg_replace( '/[\x{E0000}-\x{E007F}]/u', '', $testo );
La tentazione e’ metterla dentro sanitize_text_field() via filtro e chiudere la pratica. Non farlo in quest’ordine. La rarita’ di questi caratteri nel traffico normale e’ esattamente cio’ che li rende un segnale ad altissima confidenza, e Microsoft lo dice esplicitamente: una tecnica pensata per far sembrare i messaggi piu’ innocui offre ai difensori un indicatore con pochissimi falsi positivi. Se li togli in silenzio, butti via il segnale insieme al problema.
La sequenza corretta e’ tre passaggi, in questo ordine: rileva, registra, ripulisci. Come mu-plugin, applicato ai dati di Contact Form 7 prima che vengano salvati e spediti:
<?php
/**
* Plugin Name: LR - normalizza caratteri invisibili
* Description: Registra e rimuove i code point del blocco Tags dai dati dei form.
*/
/** Rimuove il blocco Tags U+E0000-U+E007F, lasciando intatto il resto. */
function lr_strip_tag_block( $valore ) {
return preg_replace( '/[\x{E0000}-\x{E007F}]/u', '', $valore );
}
add_filter( 'wpcf7_posted_data', function ( $dati ) {
foreach ( $dati as $campo => $valore ) {
if ( ! is_string( $valore ) ) {
continue;
}
$pulito = lr_strip_tag_block( $valore );
if ( $pulito !== $valore ) {
// Prima si registra: la presenza e' il segnale, non il testo.
error_log( sprintf(
'lr-invisibili: campo %s, %d code point rimossi, IP %s',
$campo,
mb_strlen( $valore ) - mb_strlen( $pulito ),
isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '-'
) );
}
$dati[ $campo ] = $pulito;
}
return $dati;
} );
Per Gravity Forms l’aggancio equivalente e’ gform_pre_submission_filter, per WPForms wpforms_process_filter. Il punto non e’ l’hook, e’ che il filtro stia sul dato in ingresso e non sulla resa in pagina.
Quello che non devi rimuovere
Il modo piu’ rapido di rompere un sito e’ allargare la classe di caratteri fino a comprendere tutto cio’ che e’ invisibile. Tre casi in cui la rimozione cieca fa danno, tutti verificabili in trenta secondi:
Le bandiere delle suddivisioni usano il blocco Tags legittimamente. La bandiera scozzese e’ composta da una bandiera nera piu’ sei caratteri del blocco: rimuoverli lascia una bandiera nera, che nel contesto sbagliato e’ un altro messaggio. Se sul tuo sito quelle emoji non compaiono mai, il filtro secco va benissimo; se gestisci contenuti multilingua o UGC, tratta la bandiera nera come eccezione.
Lo zero-width joiner U+200D tiene insieme le emoji composte. Toglilo e l’emoji famiglia si spezza in tre persone separate. Lo zero-width non-joiner U+200C non e’ decorativo: in persiano, in urdu e in alcune scritture indiane cambia la forma delle lettere ed e’ ortografia, non formattazione. Un filtro che lo cancella corrompe il testo.
Il byte order mark U+FEFF in testa a un file e’ un artefatto di esportazione, non un attacco: la stessa firma, cause diverse. Se lo conti insieme al resto ti riempi il log di rumore e smetti di guardarlo dopo tre giorni.
Il criterio pratico: sul blocco Tags puoi essere aggressivo perche’ fuori dalle bandiere non ha usi legittimi; sugli zero-width devi essere selettivo, e la selezione dipende dalle lingue che il tuo sito serve davvero.
La normalizzazione va messa dove entra il modello
La take che mi porto dietro da questa vicenda e’ una questione di collocazione, e vale oltre il caso specifico.
L’istinto e’ mettere il filtro all’ingresso, sul form, sul webhook, sul punto in cui il dato tocca il sistema. E’ giusto, ma non basta, perche’ un testo in azienda non entra una volta sola: entra dal form, esce nell’email di notifica, viene incollato in un ticket, ripreso in un export, riletto sei mesi dopo da uno script che nessuno ricorda. Ogni salto e’ un ingresso nuovo, e i filtri all’ingresso li metti solo dove ti ricordi che c’e’ un ingresso.
Il punto in cui la normalizzazione paga sempre e’ immediatamente prima del consumo da parte di un modello. E’ un punto solo, e’ esplicito nel codice, ed e’ lo stesso per il testo che arriva dal form, dalla posta, dal PDF o dal database. Microsoft chiude la sua guida esattamente li’: applicare la stessa normalizzazione a monte dell’ingestione AI riduce anche l’esposizione a prompt injection indiretta. Detto in termini operativi, e’ il gemello del contratto sull’output: un punto unico di controllo su cio’ che entra, un punto unico su cio’ che esce, e nessuna fiducia riposta nei passaggi intermedi.
C’e’ anche una lezione sui numeri. Questa campagna e’ stata scoperta da una firma costruita per cercare tutt’altro. Chi l’ha scritta cercava prompt injection e ha trovato phishing finanziario, perche’ il segnale che monitorava era il meccanismo, non l’intento. E’ il tipo di rilevamento che regge nel tempo: gli intenti cambiano in un weekend, i meccanismi no.
Cosa fare questa settimana
Cinque azioni, in ordine di costo crescente, tutte concludibili senza fermare niente:
- Esporta gli ultimi trecento invii del form di contatto e passaci lo script di conteggio. Dieci minuti, e ti dice se il problema esiste da te oppure no.
- Aggiungi il mu-plugin con la sequenza rileva, registra, ripulisci. Verifica dopo il primo invio reale che il log scriva e che il campo arrivi pulito.
- Cerca nei tuoi workflow n8n, Make o script il punto esatto in cui un testo di provenienza esterna entra in un prompt, e metti li’ la stessa funzione. Se i punti sono piu’ di tre, il problema vero e’ l’architettura, non i caratteri.
- Elenca gli strumenti collegati agli assistenti che leggono posta o ticket. Un modello che puo’ solo riassumere ha un rischio diverso da uno che puo’ inoltrare.
- Se hai Defender for Office 365 Plan 2 o Microsoft 365 E5, filtra la tabella EmailEvents per
DetectionMethods has "Prompt Injection Protection"e guarda cosa e’ gia’ passato.
La parte piu’ scomoda di questa storia e’ che non c’e’ niente da patchare. Nessun CVE, nessun aggiornamento da installare, nessun vendor da chiamare. C’e’ solo uno strato di testo che nessuno ha mai guardato perche’ letteralmente non si vede, e la scelta se guardarlo prima o dopo che qualcuno lo usi.
Fonti
- Microsoft Security Blog, ASCII smuggling crosses over from AI prompt injection to phishing evasion, 3 settembre 2026 (telemetria Defender for Office, domini mittente, controlli consigliati).
- Ars Technica, Once popular for attacking AI, ASCII smuggling is embraced by spammers, 4 settembre 2026 (ricostruzione del blocco Tags e attribuzione a Riley Goodside).
- WordPress Developer Resources, _sanitize_text_fields() (codice sorgente della sanitizzazione dei campi testo).
- MITRE ATT&CK T1027, Obfuscated Files or Information e MITRE ATLAS AML.T0068, LLM Prompt Obfuscation.

