
Contourne les filigranes de texte des LLM en injectant des sélecteurs de variantes Unicode ; inclut le générateur SynthID, le détecteur mean-g, les défenses par normalisation et des expériences sur l'entropie.

Oui, mais une seule famille d'attaques fonctionne, et ce n'est pas celle que tout le monde suppose.
Les sélecteurs de variation Unicode (catégorie Mn, U+FE00 à U+FE0F et U+E0100 à U+E01EF) font passer le détecteur sous le seuil et l'y maintiennent. Toutes les autres attaques par caractères invisibles que j'ai essayées sont entièrement annulées par une seule ligne de normalisation d'entrée. Les sélecteurs de variation, non, car ce sont des points de code porteurs de sens (présentation des emoji, variantes CJK) que NFKC ne repliera pas et ne doit pas replier.
Répliqué sur plusieurs modèles et domaines :
| modèle | domaine | z de référence | après vs16_30 | taux de modification |
|---|---|---|---|---|
| gpt-oss-20b | prose | 45,03 | 0,72 | 57 % |
| gpt-oss-20b | code | 37,24 | 0,68 | 58 % |
| Qwen3.8-27B | prose | 35,50 | -0,67 | 57 % |
Le seuil est z = 2,33. Les trois passent en dessous après l'attaque, et y restent après normalisation (0,09, 0,45, -0,78 respectivement). Le texte s'affiche de manière identique pour un lecteur humain.
La seconde véritable découverte ne nécessite aucune attaque : un texte à faible entropie n'est quasiment pas filigrané au départ. Avec la réflexion désactivée, la sortie purement code de Qwen3.8-27B obtient z = 4,31 sans attaque, proche du seuil de 2,33 sans qu'on lui ait rien fait. Réactivez la réflexion et le même modèle, même domaine, même longueur atteint z = 25,53, car le préambule de raisonnement est de la prose ordinaire et porte normalement la marque.
Tout dans ce dépôt a été mesuré sur ceux-ci, et rien d'autre :
| modèle | paramètres | où | ce qui a été exécuté |
|---|---|---|---|
| Qwen3.5-0.8B | 0,8B | Mac (MPS) | échelle d'attaques complète, 1k-8k |
| Qwen3.5-4B | 4B | Mac / 4090 | échelle d'attaques complète + l'expérience d'entropie + stégo brut/normalisé |
| DeepSeek-R1-Distill-Qwen-14B | 14B | Mac (MLX, 4 bits) | filigrane + détection, traces de raisonnement |
| gpt-oss-20b | 20B | 3090 / 4090 / RunPod | échelle d'attaques complète jusqu'à 32k, prose + code, sélecteurs de variation, loi d'échelle du taux d'insertion jusqu'à 131k |
| Qwen3.8-27B | 27B | RunPod H100 / Modal H100 | prose + code, sélecteurs de variation, contraste du mode réflexion |
| gpt-oss-120b | 120B | Modal H100 (MXFP4) / RunPod H100 | prose + code + raisonnement à 2k / 8k / 32k, mise à l'échelle partielle du taux d'insertion jusqu'à 65k |
| DeepSeek-V4-Flash | 284B MoE | RunPod 2x H200 (FP8) | prose + code à 2k / 8k |
Toutes les expériences n'ont pas été exécutées sur tous les modèles, et les tableaux ci-dessous indiquent ce qui a été exécuté où. Les modèles plus grands ont été ajoutés à mesure que les questions intéressantes se précisaient, de sorte que l'échelle d'attaques est étendue sur les petits et que le travail sur la longueur et les domaines est concentré sur les grands.
Anthropic (et Google DeepMind avant eux, l'article SynthID-Text) filigrane le texte généré en biaisant l'échantillonnage des tokens via un tournoi à clé. Le signal réside dans quels tokens ont été choisis, et non dans un quelconque caractère caché. Je voulais savoir à quel point c'est fragile face à une personne motivée munie d'un chercher-remplacer, j'ai donc construit le générateur ainsi qu'un détecteur g-moyen non entraîné et je l'ai soumis à des attaques.
Le seuil du détecteur est z = 2,33 (taux de faux positifs de 1 %). Au-dessus = filigrane détecté. J'ai généré du texte filigrané, l'ai attaqué, re-tokenisé et noté. Chaque tableau de résultats comporte un contrôle roundtrip (texte filigrané non attaqué) afin que vous puissiez voir que le détecteur fonctionne réellement sur cette ligne. À deux reprises au cours de cette étude, un bug a fait s'effondrer tous les z à zéro, et le contrôle est la seule chose qui l'a détecté.
La surface d'attaque se divise nettement selon la catégorie Unicode, ce qui s'est avéré être toute l'histoire.
Fonctionne (catégorie Mn, marques non espaçantes) :
| attaque | ce qu'elle fait | taux de modification | survit à la normalisation ? |
|---|---|---|---|
vs16_30 | sélecteur de variation après ~30 % des caractères | 57 % | oui |
vs16 | sélecteur de variation après ~10 % des caractères | 23 % | oui (z 3,46) |
vs_supp | sélecteurs du plan supplémentaire (U+E0100+) | 24 % | oui (z 3,40) |
homoglyph | а cyrillique pour a latin (catégorie Ll) | 9 % | oui, mais effet faible |
Ne fonctionne pas (catégorie Cf, caractères de formatage) :
| attaque | z brut | z normalisé | verdict |
|---|---|---|---|
zwsp_30 | -0.09 | 35.68 | entièrement annulé |
combo | 0.92 | 35.68 | entièrement annulé |
bidi | 24.37 | 35.68 | entièrement annulé |
nbsp | 41.04 | 46.51 | ne bouge presque pas |
Les caractères Cf sont du pur formatage sans contenu sémantique, un normaliseur peut donc les supprimer en bloc sans rien perdre. Les sélecteurs de variation Mn portent un sens réel dans les emojis et le texte CJK, les supprimer systématiquement corromprait des documents légitimes. C'est cette asymétrie qui rend une famille défendable et l'autre non.
La portée pratique : il faut toucher environ 20 % ou plus des caractères pour faire passer une baseline solide sous le seuil, mais les caractères insérés sont invisibles, le texte se lit donc normalement quel que soit le taux d'édition. Il est vérifié visuellement identique, voir src/fidelity_check.py. Quatre familles n'insèrent que des caractères qui ne s'affichent pas ; les homoglyphes sont l'honnête exception (dépendants de la police, pas garantis invisibles).
Ma crainte initiale était la plus banale : que les tirets cadratins, le remplacement de « and » par « & » et la suppression du markdown fonctionnent tout simplement, et que le watermark se révèle être de la sécurité de théâtre. Beaucoup de gens en ligne supposent exactement cela.
Ça ne fonctionne pas. Toute l'échelle d'éditions de surface sur gpt-oss-20b, jusqu'à 32k tokens :
| attaque | taux d'édition | z @ 1k | z @ 32k |
|---|---|---|---|
| aller-retour (contrôle) | 0 % | 25.7 | 104.3 |
| tiret cadratin vers trait d'union | ~0 % | 26.4 | 113.7 |
| suppression de tout le markdown | 13.6 % | 27.2 | 103.3 |
| AmE vers BrE + abréviations | 1.3 % | ~28 | ~100 |
| suppression de 40 % de chaque mot | 38 % | 4.9 | 25.4 |
Sur les trois modèles sur lesquels cette échelle a tourné (0.8B, 4B, 20B = 294 cellules), exactement une cellule a franchi le seuil : la suppression de 39 % de chaque mot à la longueur la plus courte sur le 4B. Ce n'est pas une attaque, c'est du broyage de document.
Total de cellules mesurées sur l'ensemble de l'étude : 440 (294 issues de l'échelle d'attaques ci-dessus, plus 146 issues du balayage de loi d'échelle ci-dessous étendant la prose jusqu'à 131072 tokens).
Deux choses m'ont surpris :