
Elude i watermark testuali degli LLM iniettando selettori di variazione Unicode; include il generatore SynthID, il rilevatore mean-g, le difese di normalizzazione ed esperimenti sull'entropia.

Sì, ma funziona solo una famiglia di attacchi, e non è quella che tutti presuppongono.
I variation selector Unicode (categoria Mn, da U+FE00 a U+FE0F e da U+E0100 a U+E01EF) portano il rilevatore sotto la soglia e ci restano. Ogni altro attacco basato su caratteri invisibili che ho provato viene completamente annullato da una singola riga di normalizzazione dell'input. I variation selector no, perché sono codepoint significativi (presentazione emoji, varianti CJK) che NFKC non eliminerà e non dovrebbe eliminare.
Replicato su modelli e domini diversi:
| modello | dominio | z baseline | dopo vs16_30 | tasso di modifica |
|---|---|---|---|---|
| gpt-oss-20b | prosa | 45.03 | 0.72 | 57% |
| gpt-oss-20b | codice | 37.24 | 0.68 | 58% |
| Qwen3.8-27B | prosa | 35.50 | -0.67 | 57% |
La soglia è z = 2.33. Tutti e tre scendono sotto dopo l'attacco, e restano sotto dopo la normalizzazione (rispettivamente 0.09, 0.45, -0.78). Il testo appare identico a un lettore umano.
La seconda scoperta reale non richiede alcun attacco: il testo a bassa entropia è a malapena watermarkato fin dall'inizio. Con il thinking disabilitato, l'output di puro codice di Qwen3.8-27B segna z = 4.31 senza attacco, vicino alla soglia di 2.33 senza che sia stato fatto nulla. Riattivando il thinking, lo stesso modello, stesso dominio, stessa lunghezza raggiunge z = 25.53, perché il preambolo di ragionamento è prosa ordinaria e porta il marchio normalmente.
Tutto in questo repo è stato misurato su questi, e su nient'altro:
| modello | parametri | dove | cosa è stato eseguito |
|---|---|---|---|
| Qwen3.5-0.8B | 0.8B | Mac (MPS) | scala completa di attacchi, 1k-8k |
| Qwen3.5-4B | 4B | Mac / 4090 | scala completa di attacchi + l'esperimento sull'entropia + stego raw/normalizzato |
| DeepSeek-R1-Distill-Qwen-14B | 14B | Mac (MLX, 4-bit) | watermark + rilevamento, tracce di ragionamento |
| gpt-oss-20b | 20B | 3090 / 4090 / RunPod | scala completa di attacchi fino a 32k, prosa + codice, variation selector, legge di scala del tasso di inserimento fino a 131k |
| Qwen3.8-27B | 27B | RunPod H100 / Modal H100 | prosa + codice, variation selector, confronto con thinking-mode |
| gpt-oss-120b | 120B | Modal H100 (MXFP4) / RunPod H100 | prosa + codice + ragionamento a 2k / 8k / 32k, scala parziale del tasso di inserimento fino a 65k |
| DeepSeek-V4-Flash | 284B MoE | RunPod 2x H200 (FP8) | prosa + codice a 2k / 8k |
Non ogni esperimento è stato eseguito su ogni modello, e le tabelle sottostanti indicano cosa è stato eseguito e dove. I modelli più grandi sono stati aggiunti man mano che le domande interessanti si restringevano, quindi la scala di attacchi è ampia sui modelli piccoli mentre il lavoro su lunghezza e dominio è concentrato su quelli grandi.
Anthropic (e Google DeepMind prima di loro, con il paper SynthID-Text) applica un watermark al testo generato alterando il campionamento dei token tramite un torneo con chiave. Il segnale risiede in quali token sono stati scelti, non in qualche carattere nascosto. Volevo sapere quanto questo sia fragile di fronte a una persona motivata armata di trova-e-sostituisci, così ho costruito il generatore più un rilevatore mean-g non addestrato e ho lanciato attacchi contro di esso.
La soglia del rilevatore è z = 2.33 (tasso di falsi positivi dell'1%). Al di sopra = watermark rilevato. Ho generato testo watermarkato, l'ho attaccato, ri-tokenizzato e valutato. Ogni tabella di risultati riporta un controllo roundtrip (testo watermarkato non attaccato) così puoi vedere che il rilevatore sta effettivamente funzionando in quella riga. Due volte durante questo studio un bug ha fatto collassare ogni z a zero, e il controllo è l'unica cosa che l'ha individuato.
La superficie d'attacco si divide nettamente per categoria Unicode, che si è rivelata essere l'intera storia.
Funziona (categoria Mn, segni non spazianti):
| attacco | cosa fa | tasso di modifica | sopravvive alla normalizzazione? |
|---|---|---|---|
vs16_30 | variation selector dopo ~30% dei caratteri | 57% | sì |
vs16 | variation selector dopo ~10% dei caratteri | 23% | sì (z 3.46) |
vs_supp | selector del piano supplementare (U+E0100+) | 24% | sì (z 3.40) |
homoglyph | а cirillica al posto della a latina (categoria Ll) | 9% | sì, ma effetto debole |
Non funziona (categoria Cf, caratteri di formattazione):
| attacco | z grezzo | z normalizzato | verdetto |
|---|---|---|---|
zwsp_30 | -0.09 | 35.68 | completamente ripristinato |
combo | 0.92 | 35.68 | completamente ripristinato |
bidi | 24.37 | 35.68 | completamente ripristinato |
nbsp | 41.04 | 46.51 | a malapena lo sposta |
I caratteri Cf sono pura formattazione senza contenuto semantico, quindi un normalizzatore può rimuoverli in blocco senza perdere nulla. I selettori di variazione Mn hanno un significato reale nei testi emoji e CJK, quindi rimuoverli indiscriminatamente corromperebbe documenti legittimi. È questa asimmetria a rendere difendibile una famiglia e non l'altra.
L'ambito pratico: servono circa il 20% o più di caratteri modificati per spingere una baseline forte sotto la soglia, ma i caratteri inseriti sono invisibili, quindi il testo si legge normalmente a qualsiasi tasso di modifica. È verificato visivamente identico, vedi src/fidelity_check.py. Quattro famiglie inseriscono solo caratteri che non producono alcun rendering; gli omoglifi sono l'eccezione onesta (dipendenti dal font, non garantiti invisibili).
La mia paura iniziale era quella banale: che le em-dash e "and" → "&" e la rimozione del markdown funzionassero e basta, e che la filigrana si rivelasse teatro di sicurezza. Molte persone online presumono esattamente questo.
Non funziona. L'intera scala di modifiche superficiali su gpt-oss-20b, fino a 32k token:
| attacco | tasso di modifica | z @ 1k | z @ 32k |
|---|---|---|---|
| roundtrip (controllo) | 0% | 25.7 | 104.3 |
| em-dash → trattino | ~0% | 26.4 | 113.7 |
| rimozione di tutto il markdown | 13.6% | 27.2 | 103.3 |
| AmE → BrE + abbreviazioni | 1.3% | ~28 | ~100 |
| eliminazione del 40% di ogni parola | 38% | 4.9 | 25.4 |
Su tutti e tre i modelli su cui è stata eseguita questa scala (0.8B, 4B, 20B = 294 celle), esattamente una cella ha superato la soglia: eliminare il 39% di ogni parola alla lunghezza più breve sul 4B. Non è un attacco, è fare a pezzi il documento.
Celle totali misurate nell'intero studio: 440 (294 dalla scala di attacchi sopra, più 146 dallo sweep della legge di scala sotto, che estende la prosa fino a 131072 token).
Due cose mi hanno sorpreso: