Comment Ghostchars décide de ce qu'il supprime
Un seul passage sur le texte, une décision par caractère, et une raison lisible pour chaque décision. Aucun modèle, aucune heuristique, aucune note.
Quatre passes
Le nettoyage se déroule dans un ordre fixe. Seule la troisième passe regarde les caractères un par un ; les deux premières existent pour que la troisième voie une chaîne prévisible.
Normaliser (facultatif)
Si vous l'avez demandé, le texte est d'abord normalisé en NFC ou en NFKC. C'est ce qui met en échec la stéganographie par forme de normalisation, où le message est codé dans le choix entre « é » en un seul point de code et « e » suivi d'un accent combinant. NFKC va plus loin et replie les lettres mathématiques stylisées, les ligatures et les formes pleine chasse, ce qui est destructeur et donc désactivé par défaut.
Replier les fins de ligne et les suites d'espaces (facultatif)
CRLF et CR deviennent LF ; une barre de citation du terminal en début de ligne disparaît avec l'espace qui la suit ; un tiret cadratin entouré d'espaces devient un trait d'union entouré d'espaces ; les espaces et tabulations de fin de ligne sont coupés ; les suites d'espaces à l'intérieur d'une ligne se réduisent à un seul. Ce sont des réécritures de motifs sur toute la chaîne plutôt que des décisions caractère par caractère, et chacune est une option distincte parce que chacune modifie du texte que vous voyez.
Classer chaque caractère
Chaque caractère est comparé aux tables du moteur dans un ordre fixe, et la première correspondance l'emporte. Le résultat, c'est une catégorie, l'option qui la régit, et ce par quoi le caractère doit être remplacé : une chaîne vide pour « supprimer », un espace ordinaire pour un espace exotique, une lettre ASCII pour une lettre sosie. Les caractères ordinaires ne correspondent à rien et ressortent intacts.
Reconstruire, en tenant compte du contexte
La sortie est assemblée caractère par caractère. Un caractère dont l'option est désactivée est recopié tel quel et signalé comme trouvé mais non ciblé. Un caractère dont l'option est active est supprimé ou remplacé. Tout du long, le moteur se souvient du dernier caractère qu'il a réellement CONSERVÉ, et non du dernier qu'il a vu, parce que c'est à celui-là que s'accroche un liant ou un sélecteur de variante, et qu'un liant ne fait pas avancer la base.
Les quatre passes portent sur des caractères, aucune ne lit une phrase. Savoir si l'écriture elle-même répète les habitudes qui se remarquent, phrases toutes de la même longueur, transitions toutes faites, densité de tirets cadratins, est une mesure à part : le rapport de style les compte et vous rend les chiffres, sans note et sans probabilité.
L'ordre des décisions
Les catégories sont testées dans cet ordre, et la première correspondance décide. L'ordre compte : un espace de largeur nulle est testé avant la règle générique des contrôles de formatage, donc elle est signalée comme caractère de largeur nulle et non comme un « Cf » anonyme.
| # | Par catégorie | Régi par |
|---|---|---|
| 1 | Largeur nulle et invisibles | Passe par défaut (toujours actif) |
| 2 | Forçages bidirectionnels | Passe par défaut (toujours actif) |
| 3 | Marques bidirectionnelles | Marques bidi --bidi |
| 4 | Liants, sélecteurs et remplisseurs | Agressif -a |
| 5 | Tout autre contrôle de formatage | Passe par défaut (toujours actif) |
| 6 | Caractères à usage privé | Caractères à usage privé --pua |
| 7 | Espaces exotiques | Espaces -s |
| 8 | Séparateurs de ligne | Fins de ligne -n |
| 9 | Caractères de contrôle | Caractères de contrôle --controls |
| 10 | Ponctuation typographique | Ponctuation --punct |
| 11 | Barres de citation du terminal | Barres de citation --gutters |
| 12 | Lettres sosies | Lettres sosies --confusables |
Deux exemptions se glissent dans cet ordre. L'arabe, le syriaque et le kaithi ont des contrôles de formatage qui relèvent de l'orthographe ordinaire, comme les signes de nombre arabes ou le signe de nombre kaithi. Ceux-là sont testés avant la règle générique et laissés en place. La tabulation, le saut de ligne et le retour chariot sont exclus du groupe des caractères de contrôle, parce que les supprimer collerait les lignes les unes aux autres.
Pourquoi le contexte décide
Quatre familles de caractères sont à la fois invisibles ET porteuses : les liants, les sélecteurs de variante, les remplisseurs propres à certaines écritures et les marques de direction. Le même point de code peut être de la contrebande à un endroit et l'orthographe correcte à un autre : le moteur regarde donc les voisins avant de décider. C'est la partie que la plupart des nettoyeurs ratent, et dans les deux sens : ils laissent les caractères cachés ou ils cassent les vrais.
L'emoji famille
Un emoji famille, ce sont trois ou quatre personnes soudées par des liants de largeur nulle. Supprimez-les et un emoji en devient trois. Ghostchars conserve un liant dont les deux voisins sont des bases emoji, et supprime le même point de code lorsqu'il se trouve entre deux lettres latines, là où il ne joint rien et se cache bien.
Le mot persan
Le persan écrit ses formes continues et ses pluriels avec un non-liant de largeur nulle : ce n'est pas de la décoration, c'est l'orthographe. Ghostchars conserve un liant ou un non-liant quand les caractères des deux côtés appartiennent à la même écriture liante (arabe, persan, devanagari, malayalam), et le supprime partout ailleurs. La même règle protège les voyelles khmères, les sélecteurs mongols et les remplisseurs de jamos hangul auprès de leurs propres écritures.
Le drapeau écossais
Un drapeau de subdivision, c'est un drapeau noir suivi de caractères de balise qui épellent un code de région, puis d'une balise d'annulation. Ces mêmes caractères de balise, isolés, constituent l'attaque par contrebande ASCII : des instructions invisibles qu'un modèle de langue lira. Ghostchars analyse toute la séquence : les caractères de balise à l'intérieur d'un drapeau complet sont conservés, et tous ceux qui sont en dehors sont supprimés.
La phrase en hébreu
Les marques de gauche à droite et de droite à gauche sont ce qui permet à un texte bidirectionnel de garder sa ponctuation du bon côté. Ghostchars conserve une marque bidi dont le voisin est un caractère de droite à gauche, et supprime celle qui dérive dans un paragraphe anglais, où sa seule fonction est de transporter un bit. Les cinq contrôles de forçage et d'enchâssement n'ont droit à aucune indulgence : ils peuvent réordonner un texte que vous n'avez pas écrit, donc ils partent toujours.
Chacune de ces décisions est signalée. Un caractère conservé est listé comme « conservé, porteur », avec l'option qui le supprimerait malgré tout : rien n'est décidé en silence et rien ne vous est caché.
Vérifié contre onze techniques publiées
L'outil en ligne de commande est livré avec un test adverse qui implémente onze techniques documentées de dissimulation dans le texte, sous forme de paires codeur/décodeur. Pour chacune, il enfouit un secret, nettoie le texte, et vérifie deux choses : que le décodeur ne peut plus récupérer le secret, et que le texte de couverture visible ressort octet pour octet identique.
| Technique | Mise en échec par |
|---|---|
| Zero-width binary (ZWSP / ZWNJ / ZWJ) | la passe par défaut |
| Word-joiner and invisible-maths binary | la passe par défaut |
| Tag-block ASCII smuggling | la passe par défaut |
| Variation-selector byte smuggling | la passe par défaut |
| Bidi mark binary (LRM / RLM) | la passe par défaut |
| SNOW trailing whitespace | Espaces de fin de ligne --trailing |
| Cyrillic homoglyph binary | Lettres sosies --confusables |
| NFC / NFD normalisation-form binary | Normaliser (NFC) --nfc |
| NBSP / space homoglyph binary | Espaces -s |
| Single / double space binary | Fusionner les espaces --collapse |
| Curly / straight apostrophe binary | Ponctuation --punct |
Cinq des onze tombent avec la seule passe par défaut. Les six autres demandent une option, parce que le porteur est un caractère que vous voyez, un espace, une apostrophe ou une lettre sosie, et que les supprimer sans qu'on le demande changerait le texte.
Le même moteur à deux endroits
Le moteur du navigateur est un portage de l'outil Python en ligne de commande, pas une réinterprétation.
Un générateur exécute l'implémentation Python sur un corpus de fixtures (chaque point de code listé, chaque cas de contexte, les onze charges de stéganographie, de la prose mixte réaliste) pour quatorze combinaisons d'options, et enregistre la sortie nettoyée ainsi que chaque caractère trouvé. Le moteur TypeScript est testé contre cet enregistrement. Le moindre écart entre les deux fait échouer le build : « le site fait ce que fait la CLI » est donc un résultat de test, pas une promesse.
Cela compte pour une raison pratique : vous pouvez vérifier un fichier dans un hook pre-commit avec la CLI et obtenir exactement le résultat que le site vous aurait donné.
Le même moteur tourne à un troisième endroit. Le rapport du document décompresse un .docx ou un .odt et lit le XML qu'il contient plutôt qu'une chaîne collée : un caractère reçoit donc dans un fichier le verdict qu'il reçoit dans le champ de la page d'accueil, et les passages masqués, les modifications suivies et les métadonnées autour de lui sont signalés sans être supprimés.
Ce que cet outil ne peut pas faire
Tout ce qui précède parle de caractères. Ces quatre choses-là ne relèvent pas des caractères, et aucun nettoyage n'y touche.
- Les filigranes statistiques: Des dispositifs comme SynthID-Text ou la méthode de la « liste verte » de Kirchenbauer biaisent les MOTS que le modèle retient. Le signal vit dans le choix des mots sur tout un passage, pas dans un caractère. Seule une réécriture du texte le change, et cet outil ne réécrit pas de texte.
- La détection stylométrique: Des détecteurs comme GPTZero notent la prévisibilité de chaque mot et la variation de cette prévisibilité. Supprimer un espace de largeur nulle ne déplace pas cette note d'un seul point. Le rapport de style mesure les habitudes modifiables qui se trouvent derrière (variation de la longueur des phrases, mots révélateurs, transitions toutes faites) et refuse quand même de sortir une probabilité.
- Ce qui se cache dans le format: Le texte blanc sur blanc, les passages masqués, les commentaires HTML et les métadonnées de PDF se cachent EN DEHORS du texte. L'outil Documents les signale : passages masqués, modifications suivies, commentaires, identifiants de session d'édition, métadonnées d'auteur. Il ne les supprime délibérément pas, parce que les supprimer change le document, et que cela doit rester votre décision.
- Les canaux à moins d'un bit par emoji: Une chaîne de liants entre emojis et un sélecteur de variante après un emoji sont conservés par défaut, parce que c'est comme ça que fonctionnent les emojis. Un expéditeur déterminé peut malgré tout y cacher une petite quantité de données. Activez Agressif si vous voulez les faire disparaître, en acceptant que vos emojis se disloquent.
Ghostchars ne produit aucune probabilité, aucune note et aucun verdict sur l'auteur d'un texte. Il vous dit quels caractères se trouvent dans votre chaîne, où ils sont, et ce qui arrive à chacun.
Questions
Pourquoi ne pas supprimer tous les caractères invisibles ?
Parce que plusieurs d'entre eux sont porteurs. Supprimer tous les liants de largeur nulle transforme un emoji famille en trois personnes séparées et écrit les mots persans de travers. Supprimer toutes les marques bidi casse la mise en page d'un texte mêlant hébreu et anglais. Un nettoyeur qui fait cela discrètement est pire que pas de nettoyeur du tout, parce que vous ne voyez pas ce qu'il a emporté.
La version navigateur est-elle identique à l'outil en ligne de commande ?
Oui, et c'est testé. Des fixtures de référence générées depuis l'implémentation Python fixent le moteur TypeScript à une sortie octet pour octet identique, pour chaque fixture et chaque combinaison d'options ; la moindre différence fait échouer le build.
Peut-il me dire si un texte a été écrit par une IA ?
Non, et il ne le prétendra jamais. Les caractères invisibles vous disent qu'un texte est passé par un logiciel. Ils ne vous disent pas lequel, et ils ne vous disent pas qui a écrit les mots. Tout outil qui transforme un nombre de caractères en pourcentage de confiance invente ce chiffre.
Mon texte quitte-t-il mon navigateur ?
Non. Il n'y a aucun traitement côté serveur et rien vers quoi téléverser : le moteur est du JavaScript qui tourne sur votre appareil, et le site est un ensemble de fichiers statiques. Vous pouvez vous déconnecter du réseau, tout continue de fonctionner.