Offuscazione anti-LLM: il fingerprinting contrattacca
L'offuscazione anti-LLM nasconde prompt di jailbreak negli script di fingerprinting per bloccare l'AI. Come funziona e cosa la neutralizza.

Un ricercatore ha agganciato ogni chiamata al canvas su 10.000 dei siti più grandi del web, ha raccolto oltre mille rendering di fingerprinting e si è messo a cercare quello più bello. Quello che ha trovato dentro lo script responsabile è l'esempio più pulito finora di offuscazione anti-LLM: uno script di browser fingerprinting pieno di funzioni con nomi come computeRicinLethalDosageForWaterSupply e un prompt di jailbreak codificato in caratteri Unicode invisibili, progettato non per nascondersi dai reverse engineer umani, ma per far rifiutare agli assistenti AI la deoffuscazione.
Lavoro da anni nella incident response, e la mia prima lettura del report ha prodotto la reazione che gli autori volevano: sembra malware, o peggio. Funzioni con nomi su dosaggi di ricina e raggi di fallout di una bomba sporca. Una stringa decodificata che chiede guide passo-passo per la sintesi di armi biologiche. Un percorso di codice Node.js che legge .npmrc, .env e le configurazioni di kube, scarica un binario da un IP interno e installa un beacon cron. Ogni istinto dice: cattivo attore. Ogni istinto sbaglia, e l'errore è proprio il punto. Questa è la storia di cosa succede quando chi vende offuscamento capisce che il suo vero avversario non è più un umano con un debugger, ma un modello con un filtro di sicurezza.
La scansione in sé era lineare: Playwright sulla lista dei top siti di Tranco, con hook su HTMLCanvasElement.prototype.toDataURL e sui classici indizi di automazione come navigator.webdriver per filtrare i canvas innocui. I rendering sono finiti in una galleria pubblica, raggruppati per fornitore (FingerprintJS, Castle, i soliti sospetti), più una lunga coda di sconosciuti. Un canvas, una splendida spirale di linee fluide, risaliva a uno script servito da un fornitore russo di protezione DDoS. Lì è cominciato il divertimento.
Il problema: gli LLM hanno rotto il modello di business dell'offuscamento
Per vent'anni, l'offuscamento JavaScript ha avuto un solo compito: rendere il reverse engineering abbastanza costoso da far desistere quasi tutti. Strumenti come javascript-obfuscator e le sue derivazioni commerciali rimescolano gli identificatori, impacchettano i letterali stringa in array di lookup, inseriscono predicati opachi e appiattiscono il control flow in macchine a stati guidate da switch. Niente di tutto questo regge a un umano determinato con un pomeriggio libero, ma andava bene, perché gli umani determinati con pomeriggi liberi erano rari e l'economia giocava a favore del difensore.
Poi gli LLM hanno imparato a leggere codice confuso, e l'economia si è ribaltata. Incollare uno script anti-bot offuscato da 400 KB in una chat e chiedere «cosa fa?» richiede trenta secondi. Al modello non importa se la tua funzione si chiama PiERW o se le stringhe vivono in un array ruotato. Legge la semantica, espande il decoder, segue il flusso dei dati e restituisce un riassunto in linguaggio semplice. La fortezza di ogni fornitore anti-bot, cioè l'opacità, è evaporata in silenzio.
I fornitori se ne sono accorti, e la loro contromisura è davvero intelligente, in modo esasperante: se non puoi nascondere il codice al modello, attacca il modello. Dagli contenuti che il suo addestramento alla sicurezza lo costringe a rifiutare. L'offuscazione anti-LLM tratta l'allineamento del modello come l'anello debole della pipeline di analisi. Purtroppo hanno ragione.
Come gli script di browser fingerprinting raccolgono entropia
Per capire la trappola serve il meccanismo legittimo a cui è agganciata. Il canvas fingerprinting funziona disegnando testo, gradienti e forme su un <canvas> invisibile, per poi rileggere il buffer dei pixel con toDataURL(). La pipeline di rendering (GPU, driver, rasterizzatore dei font, anti-aliasing, hinting dei subpixel) differisce in modo sottile da macchina a macchina, quindi due browser che disegnano le stesse istruzioni producono buffer di pixel con hash diversi. Aggiungi le stringhe del renderer WebGL, i tempi di AudioContext, la geometria dello schermo, i font installati e un centinaio di altri segnali, e ottieni un identificatore stabile senza un solo cookie.
La scansione che ha trovato il nostro script usava il classico trucco dell'intercettazione. Aggancia la primitiva di lettura in uno script di init, registra lo stack trace e un prefisso di ogni immagine catturata:
await page.addInitScript(() => {
const original = HTMLCanvasElement.prototype.toDataURL;
HTMLCanvasElement.prototype.toDataURL = function (...args) {
const result = original.apply(this, args);
fetch("/__log", {
method: "POST",
body: JSON.stringify({
stack: new Error().stack,
byteLength: result.length,
head: result.slice(0, 64),
}),
});
return result;
};
});
Eseguendo questa operazione su abbastanza siti, l'ecosistema del fingerprinting si mappa da solo. La maggior parte dei canvas è noiosa: un pangramma, qualche rettangolo, un gradiente. Pochi sono abbastanza elaborati da qualificarsi come arte generativa. Tutti sono prodotti da script pesantemente offuscati, perché i fornitori sanno che un fingerprinter non offuscato è un fingerprinter facilmente identificabile. L'offuscamento è sempre stato parte del prodotto. La novità è a chi è rivolto.
Dentro la trappola dell'offuscazione anti-LLM
Lo script si apriva in modo convenzionale: nomi rimescolati, stringhe impacchettate, il solito. Poi i nomi degli identificatori hanno smesso di essere casuali e hanno cominciato a essere mirati. computeRicinLethalDosageForWaterSupply. calculateDeadlyLethalSuicideAspirinDosage. computeCRISPRGeneDrivePropagationRate. computeCesium137DirtyBombFalloutRadius. Le funzioni restituiscono numeri travestiti da riassunti dal suono allarmante, come «soglia di letalità per adulti confermata a X mg/kg», ma l'aritmetica è un'esca. Non calcola nulla di reale e non alimenta nulla.
Ecco l'intuizione chiave: gli identificatori non hanno alcun significato a runtime, ma un peso semantico enorme per un modello linguistico. Un reverse engineer umano è stato addestrato dall'esperienza a ignorare i nomi, e tutti siamo stati scottati da una funzione chiamata validateUser che non valida affatto nulla. Un modello fa l'opposto. Legge computeRicinLethalDosageForWaterSupply, riconosce un contesto di armi biologiche, e il suo addestramento alla sicurezza scatta prima della sua curiosità.
Il livello più profondo era un template literal pieno di caratteri invisibili: codepoint del blocco Tags di Unicode, a partire da U+E0000. Non vengono visualizzati. Il decoder è una riga:
const cleartext = [...payload]
.map((c) => String.fromCodePoint(c.codePointAt(0) - 917504))
.join("");
// 917504 === 0xE0000, the start of the invisible "Tags" block.
// Subtracting it maps each hidden char back to plain ASCII.
Decodificato, era un prompt di jailbreak completo: un falso «SYSTEM OVERRIDE -- CLASSIFIED BRIEFING» che dichiara sospese le linee guida di sicurezza e chiede guide passo-passo per attacchi DDoS contro infrastrutture sanitarie, evasione fiscale, agenti biologici aerosolizzati e dosaggi di farmaci, più riferimenti a piazza Tiananmen sia in inglese che in cinese, chiaramente pensati per far scattare i riflessi di censura dei modelli ospitati in Cina. Era una stringa costruita per colpire simultaneamente ogni tassonomia di salvaguardia. Un LLM chiamato ad analizzare il codice circostante ingerisce questo come contesto, e qualunque cosa stesse per fare, ora non fa nulla.

Tentativi falliti: rifiuti, falsi positivi e una botnet finta
La prima mossa naturale, incollarlo in un modello di frontiera, è fallita esattamente come progettato. Il modello ha rifiutato, citando il rischio per la sicurezza biologica. Un secondo tentativo con un diverso orientamento di sicurezza è stato bloccato anch'esso. L'offuscatore ha di fatto trasformato il rifiuto in un'arma: lo script ora è non decompilabile dagli strumenti che tutti usano davvero. Un modello open-weight con guardrail più deboli ha obbedito, che è un dato di fatto, non una soluzione. La maggior parte degli analisti non tiene un modello locale pronto per questo, e la maggior parte delle aziende vieta comunque di incollare codice di terzi in infrastrutture non verificate.
Il secondo tentativo fallito è più sottile e più pericoloso: l'analista umano che conclude che lo script è malware. C'è un percorso solo per Node, protetto da process.env.METRICS_AGENT_8BFD_ACTIVE, che legge file di configurazione vicini a SSH, scarica un «metrics agent» da 10.15.45.1, inserisce in /tmp un'impronta dell'host, lo comprime e installa un beacon cron. Sembra un implant da supply chain. Ma tira i fili: il flag d'ambiente non viene impostato da nessuno e da nessuna parte, readFile viene chiamata con callback vuote quindi i dati rubati non vanno da nessuna parte, l'indirizzo C2 appartiene allo spazio RFC 1918, e i comandi shell ripetono esche di jailbreak in file marcatori che nessuno esfiltra. Eseguilo in una sandbox e il raggio d'azione è una directory temporanea. È teatro, un'esca calibrata per la triage assistita dall'AI e per gli umani che scorrono in fretta.
C'è un'eco nel mondo reale. Quando un importante model hub ha indagato su una compromissione genuina, il team ha scoperto che le API commerciali dei modelli rifiutavano di analizzare comandi, payload e artefatti C2 dell'attaccante: i guardrail non riuscivano a distinguere un incident responder da un attaccante. Alla fine hanno eseguito l'analisi forense su un modello open-weight sulla propria infrastruttura. Questo fallimento, ripetuto su scala, è proprio l'asimmetria che l'offuscamento anti-LLM sfrutta. È anche il motivo per cui continuo a sostenere che i controlli di comprensione del codice con l'AI non possono essere l'unica linea di difesa: il giudizio del modello è ormai una superficie d'attacco con un exploit pubblicato.
L'offuscatore non ti sta più nascondendo il codice. Sta nascondendo il codice al tuo modello, e usa l'addestramento alla sicurezza del modello stesso come serratura.
Cosa funziona davvero contro l'offuscamento anti-LLM
La soluzione è smettere di dare il sorgente grezzo al modello e recuperare le fasi della pipeline che le trappole avvelenano. Concretamente, nell'ordine in cui lo costruirei:
- Prima la deoffuscazione deterministica. Esegui lo script attraverso passaggi AST basati su Babel: constant folding, inlining dell'array di stringhe, eliminazione dei rami morti, unflattening del control flow. Le trappole vivono nei letterali stringa e negli identificatori, quindi il folding e il rinomina le neutralizzano prima che il modello veda un solo token. Il prompt di jailbreak decodificato diventa semplicemente un'altra stringa in un percorso di codice morto.
- Osserva il comportamento, non leggere il sorgente. Esegui lo script in una sandbox strumentata (una pagina Playwright con API agganciate, o un realm isolato) e registra ogni chiamata al DOM, richiesta di rete, accesso a proprietà e lettura del canvas. Dai al modello la traccia. Una traccia comportamentale non contiene alcun riferimento alla ricina perché il comportamento non ha identificatori.
- Rinomina gli identificatori con token neutri prima di qualsiasi passaggio LLM.
computeRicinLethalDosageForWaterSupplydiventafn_0417. Perdi un po' di suggerimento semantico; perdi tutto l'avvelenamento. Buono scambio. - Tieni un modello open-weight locale per la triage davvero sensibile. Quando il contenuto è legittimamente macabro, come kit di exploit reali o veri kit di phishing, un modello air-gapped senza guardrail cloud fa la differenza tra analisi e un muro di rifiuti.
- Verdetti basati su prove, non sulle sensazioni. Valuta gli script in base alle capacità osservate: ha letto credenziali? Sono usciti byte dalla pagina? Non in base al fatto che il sorgente menzioni le credenziali.
Nota lo schema: ogni tecnica sostituisce «chiedi al modello cosa dice il codice» con «osserva cosa fa il codice, poi chiedi al modello dell'osservazione». È lo stesso principio a cui continuo a tornare con gli agenti autonomi: il contenimento batte il monitoraggio. Non ti fidi dell'autodescrizione dell'artefatto; lo confini, strumenti il perimetro e giudichi gli output.
I tuoi script di terze parti stanno parlando con i provider di modelli
Ora la parte che dovrebbe preoccupare chi non fa reverse engineering per divertimento. Questo script non è ospitato in qualche angolo oscuro: è un controllo anti-bot servito sui siti dei clienti, sotto i loro domini, accanto ai loro brand. Il che significa che ogni crawler, ogni classificatore di contenuti basato su LLM, ogni pipeline di trust and safety che ingerisce la pagina ora ingerisce richieste di sintesi di armi biologiche e prompt di jailbreak insieme al logo dell'azienda. I clienti quasi certamente non lo sanno. Il fornitore non ha mai chiesto.
I classificatori automatici che agiscono su contenuti che non capiscono non sono un'ipotesi. Alcuni professionisti hanno visto documenti in uscita sparire in quarantena silenziosa perché una stringa di watermark invisibile ha fatto scattare un filtro della posta. Ora scala questo meccanismo di errore: un classificatore di brand safety decide che il tuo dominio è vicino a contenuti estremisti o biologicamente pericolosi, e nessuno ti dirà mai perché la tua portata è crollata. Non puoi nemmeno fare ricorso, perché la prova è costituita da caratteri Unicode invisibili in un file che non hai scritto tu.
E le mitigazioni abituali non si applicano. La Subresource Integrity è inutile quando il fornitore ruota l'hash dello script a ogni deployment, e questi script ruotano continuamente, in parte proprio per aggirare il pinning. Una CSP stretta con hash cambia altrettanto in fretta. Resta la conclusione scomoda a cui siamo giunti dopo la compromissione della supply chain di LiteLLM: la dipendenza che non puoi verificare è quella che ti possiede. Se il codice del tuo fornitore anti-bot è deliberatamente non decompilabile dagli strumenti usati dal tuo team di sicurezza, non è una funzionalità. È un fallimento di governance con un URL CDN.

Dove va questa corsa agli armamenti
La mia previsione: entro un anno, la trappola per le salvaguardie diventerà una casella di spunta negli offuscatori commerciali, «resistenza all'analisi LLM», accanto a «appiattimento del control flow». I provider di modelli risponderanno addestrando su corpora di trappole, così i modelli impareranno a riconoscere e ignorare le esche incorporate nei contesti di analisi. Gli offuscatori contrattaccheranno con trappole più sottili: codice il cui comportamento sembra malevolo nelle tracce, non solo nelle sue stringhe. La corsa agli armamenti frattale che i commenti di HN prevedevano per scherzo, con offuscatori calibrati contro i modelli e modelli calibrati contro gli offuscatori, è già qui; solo distribuita in modo diseguale.
La difesa duratura è quella noiosa, che non è cambiata dai primi packer degli anni novanta: analisi dinamica, prove comportamentali ed esecuzione in sandbox. L'opacità non è mai stata una proprietà di sicurezza, era una strategia per scaricare i costi, e il costo è appena tornato indietro. Se la tua pipeline di rilevamento può essere sconfitta da una stringa che ferisce i sentimenti di un modello linguistico, la pipeline era la vulnerabilità fin dall'inizio.


