Zum Inhalt springen
Ghostchars

So entscheidet Ghostchars, was verschwindet

Ein Durchgang durch den Text, eine Entscheidung pro Zeichen und für jede Entscheidung ein Grund, den du nachlesen kannst. Kein Modell, keine Heuristik, kein Score.

Vier Durchgänge

Das Säubern läuft in fester Reihenfolge. Nur der dritte Durchgang schaut auf einzelne Zeichen; die ersten beiden gibt es, damit der dritte eine vorhersagbare Zeichenkette vor sich hat.

  1. Normalisieren (optional)

    Wenn du danach fragst, wird der Text zuerst nach NFC oder NFKC normalisiert. Das knackt die Steganografie über Normalisierungsformen, bei der eine Nachricht in der Wahl zwischen »é« als einem Codepoint und »e« plus kombinierendem Akzent steckt. NFKC geht weiter und faltet gestylte mathematische Buchstaben, Ligaturen und Vollbreitenformen; das ist verlustbehaftet und deshalb standardmäßig aus.

  2. Zeilenenden und Leerraum falten (optional)

    CRLF und CR werden zu LF; ein Zitatbalken aus dem Terminal am Zeilenanfang fällt samt dem Leerzeichen danach weg; ein Gedankenstrich mit Leerzeichen wird zu einem Bindestrich mit Leerzeichen; Leerzeichen und Tabulatoren am Zeilenende fallen weg; mehrere Leerzeichen innerhalb einer Zeile werden zu einem. Das sind Musterersetzungen über die ganze Zeichenkette statt Entscheidungen pro Zeichen, und jede ist eine eigene Option, weil jede sichtbaren Text verändert.

  3. Jedes Zeichen einordnen

    Jedes Zeichen wird in fester Reihenfolge gegen die Tabellen der Engine geprüft, und der erste Treffer gewinnt. Heraus kommen eine Kategorie, die zuständige Option und das, wodurch das Zeichen ersetzt werden soll: eine leere Zeichenkette für »löschen«, ein normales Leerzeichen für ein exotisches Leerzeichen, ein ASCII-Buchstabe für einen ähnlich aussehenden. Gewöhnliche Zeichen treffen auf nichts und kommen unverändert zurück.

  4. Neu zusammensetzen, mit Kontext

    Die Ausgabe wird Zeichen für Zeichen aufgebaut. Ein Zeichen, dessen zuständige Option aus ist, wird durchgereicht und als »gefunden, nicht angetastet« gemeldet. Ein Zeichen, dessen Option an ist, wird entfernt oder ersetzt. Dabei merkt sich die Engine durchgehend das letzte Zeichen, das sie tatsächlich BEHALTEN hat, nicht das letzte, das sie gesehen hat, denn das ist die Basis, an die sich ein Verbinder oder ein Variantenselektor hängt, und ein Klebezeichen wird selbst nie zur Basis.

Alle vier Durchgänge drehen sich um Zeichen, keiner davon liest einen Satz. Ob der Text selbst die Gewohnheiten wiederholt, die Leuten auffallen, also gleich lange Sätze, Standardüberleitungen, viele Gedankenstriche, ist eine eigene Messung: Der Stilbericht zählt sie und gibt dir die Zahlen, ohne Punktzahl und ohne Wahrscheinlichkeit.

Die Reihenfolge der Entscheidungen

Die Kategorien werden in dieser Reihenfolge geprüft, und der erste Treffer entscheidet. Die Reihenfolge zählt: Ein Leerzeichen ohne Breite wird vor der allgemeinen Regel für Formatsteuerzeichen geprüft und deshalb als Zeichen ohne Breite gemeldet und nicht als anonymes »Cf«.

#Nach KategorieZuständige Option
1Ohne Breite und unsichtbarVoreinstellung (immer an)
2Richtungs-OverridesVoreinstellung (immer an)
3RichtungsmarkenRichtungsmarken --bidi
4Verbinder, Selektoren und FüllzeichenAggressiv -a
5Jedes andere FormatsteuerzeichenVoreinstellung (immer an)
6Zeichen aus dem PrivatbereichZeichen aus dem Privatbereich --pua
7Exotische LeerzeichenLeerzeichen -s
8ZeilentrennerZeilenenden -n
9SteuerzeichenSteuerzeichen --controls
10Typografische SatzzeichenSatzzeichen --punct
11Zitatbalken aus dem TerminalZitatbalken --gutters
12Ähnlich aussehende BuchstabenÄhnlich aussehende Buchstaben --confusables

Zwei Ausnahmen sitzen innerhalb dieser Reihenfolge. Arabisch, Syrisch und Kaithi haben Formatsteuerzeichen, die schlicht Rechtschreibung sind (die arabischen Zahlzeichen, das Kaithi-Zahlzeichen), und die greifen vor der allgemeinen Regel und bleiben unangetastet. Tabulator, Zeilenvorschub und Wagenrücklauf sind von der Gruppe der Steuerzeichen ausgenommen, weil ihr Löschen Zeilen zusammenziehen würde.

Warum der Kontext entscheidet

Vier Zeichenfamilien sind unsichtbar UND tragend: Verbinder, Variantenselektoren, schriftspezifische Füllzeichen und Richtungsmarken. Derselbe Codepoint kann an einer Stelle Schmuggelware und an einer anderen die korrekte Schreibweise sein, deshalb schaut die Engine auf die Nachbarn, bevor sie entscheidet. Genau das machen die meisten Reiniger falsch, und zwar in beide Richtungen: Sie lassen die versteckten stehen oder sie zerlegen die echten.

Das Familien-Emoji

Ein Familien-Emoji sind drei oder vier Personen, zusammengeschweißt von Verbindern ohne Breite. Nimm sie weg, und aus einem Emoji werden drei. Ghostchars behält einen Verbinder, dessen Nachbarn auf beiden Seiten Emoji-Basen sind, und entfernt denselben Codepoint, wenn er zwischen zwei lateinischen Buchstaben steht; dort verbindet er nichts und versteckt sich gut.

Das persische Wort

Persisch schreibt seine Verlaufs- und Pluralformen mit einem Trenner ohne Breite: Das ist keine Dekoration, das ist die Rechtschreibung. Ghostchars behält einen Verbinder oder Trenner, wenn die Zeichen auf beiden Seiten zur selben verbindenden Schrift gehören (Arabisch, Persisch, Devanagari, Malayalam), und entfernt ihn überall sonst. Dieselbe Regel schützt Khmer-Vokale, mongolische Selektoren und Hangul-Jamo-Füllzeichen neben ihrer eigenen Schrift.

Die schottische Flagge

Eine Regionalflagge ist eine schwarze Flagge, gefolgt von Tag-Zeichen, die einen Regionscode buchstabieren, und einem Abschluss-Tag. Genau dieselben Tag-Zeichen sind, frei stehend, der ASCII-Smuggling-Angriff: unsichtbare Anweisungen, die ein Sprachmodell mitliest. Ghostchars liest die ganze Sequenz: Tag-Zeichen innerhalb einer vollständigen Flagge bleiben, jedes Tag-Zeichen außerhalb wird entfernt.

Der hebräische Satz

Die Marken für links-nach-rechts und rechts-nach-links sorgen dafür, dass Text mit gemischter Laufrichtung seine Satzzeichen auf der richtigen Seite behält. Ghostchars behält eine Richtungsmarke, deren Nachbar ein Zeichen von rechts nach links ist, und entfernt eine, die allein in einem deutschen Absatz treibt, wo ihre einzige Funktion darin besteht, ein Bit zu tragen. Die fünf Override- und Einbettungssteuerzeichen bekommen diesen Vertrauensvorschuss nicht: Sie können Text umordnen, den du nicht geschrieben hast, und fliegen deshalb immer raus.

Jede einzelne dieser Entscheidungen wird gemeldet. Ein behaltenes Zeichen steht als »behalten, wird gebraucht« in der Liste, zusammen mit der Option, die es trotzdem entfernen würde. Nichts wird still entschieden und nichts vor dir verborgen.

Gegen elf veröffentlichte Verfahren geprüft

Das Kommandozeilenwerkzeug bringt einen Angriffstest mit, der elf dokumentierte Techniken zum Verstecken von Text als Encoder-Decoder-Paare umsetzt. Für jede bettet er ein Geheimnis ein, säubert den Text und prüft zwei Dinge: dass der Decoder das Geheimnis nicht mehr rekonstruieren kann und dass der sichtbare Trägertext Byte für Byte identisch überlebt.

VerfahrenGeknackt mit
Zero-width binary (ZWSP / ZWNJ / ZWJ)Voreinstellung
Word-joiner and invisible-maths binaryVoreinstellung
Tag-block ASCII smugglingVoreinstellung
Variation-selector byte smugglingVoreinstellung
Bidi mark binary (LRM / RLM)Voreinstellung
SNOW trailing whitespaceLeerraum am Zeilenende --trailing
Cyrillic homoglyph binaryÄhnlich aussehende Buchstaben --confusables
NFC / NFD normalisation-form binaryNormalisieren (NFC) --nfc
NBSP / space homoglyph binaryLeerzeichen -s
Single / double space binaryLeerzeichen zusammenfassen --collapse
Curly / straight apostrophe binarySatzzeichen --punct

Fünf der elf fallen schon durch die Voreinstellung allein. Die anderen sechs brauchen eine Option, weil ihr Träger ein Zeichen ist, das du sehen kannst (ein Leerzeichen, ein Apostroph, ein ähnlich aussehender Buchstabe), und diese ungefragt zu entfernen würde den Text verändern.

Dieselbe Engine an zwei Orten

Die Browser-Engine ist eine Portierung des Python-Kommandozeilenwerkzeugs, keine Neuauslegung davon.

Ein Generator lässt die Python-Implementierung über ein Fixture-Korpus laufen (jeden gelisteten Codepoint, jeden Kontextfall, alle elf Steganografie-Nutzlasten, realistischen gemischten Fließtext), und zwar für vierzehn Optionskombinationen, und schreibt die gesäuberte Ausgabe und jeden Fund mit. Die TypeScript-Engine wird gegen diese Aufzeichnung getestet. Jede Abweichung zwischen beiden lässt den Build scheitern. »Die Website tut, was das CLI tut« ist damit ein Testergebnis und kein Versprechen.

Das zählt aus einem praktischen Grund: Du kannst eine Datei im Pre-Commit-Hook mit dem CLI prüfen und bekommst genau das Ergebnis, das dir die Website gegeben hätte.

Dieselbe Engine läuft an einer dritten Stelle. Der Dokumentbericht entpackt eine .docx oder .odt und liest das XML darin statt einer eingefügten Zeichenkette. Ein Zeichen bekommt in der Datei also dasselbe Urteil wie im Feld auf der Startseite, und die ausgeblendeten Textabschnitte, nachverfolgten Änderungen und Metadaten drumherum werden gemeldet, aber nicht gelöscht.

Was das hier nicht kann

Alles oben dreht sich um Zeichen. Diese vier Dinge drehen sich nicht um Zeichen, und kein noch so gründliches Säubern rührt sie an.

  • Statistische Wasserzeichen: Verfahren wie SynthID-Text und die »Green List«-Methode von Kirchenbauer beeinflussen, welche WÖRTER ein Modell wählt. Das Signal steckt in der Wortwahl über eine ganze Passage hinweg, nicht in irgendeinem Zeichen. Nur das Umschreiben des Textes ändert es, und dieses Werkzeug schreibt keinen Text um.
  • Stilometrische Erkennung: Detektoren wie GPTZero bewerten, wie vorhersagbar jedes Wort ist und wie stark diese Vorhersagbarkeit schwankt. Ein entferntes Leerzeichen ohne Breite bewegt diesen Wert um keinen einzigen Punkt. Der Stilbericht misst die Gewohnheiten dahinter, an denen du wirklich etwas ändern kannst (Schwankung der Satzlänge, Verräterwörter, Standardüberleitungen), und weigert sich trotzdem, eine Wahrscheinlichkeit auszugeben.
  • Verstecke auf Formatebene: Weiß auf Weiß, ausgeblendete Textabschnitte, HTML-Kommentare und PDF-Metadaten verstecken sich AUSSERHALB des Textes. Das Dokumentwerkzeug meldet sie (ausgeblendete Abschnitte, nachverfolgte Änderungen, Kommentare, Kennungen von Bearbeitungssitzungen, Metadaten zur Urheberschaft), löscht sie aber bewusst nicht, denn Löschen verändert das Dokument, und das sollte deine Entscheidung sein.
  • Kanäle mit weniger als einem Bit pro Emoji: Eine Kette aus Verbindern zwischen Emoji und ein Variantenselektor dahinter bleiben standardmäßig erhalten, weil Emoji genau so funktionieren. Wer es darauf anlegt, kann dort weiterhin eine kleine Datenmenge verstecken. Schalte »Aggressiv« ein, wenn sie weg müssen, und nimm in Kauf, dass deine Emoji auseinanderfallen.

Ghostchars gibt keine Wahrscheinlichkeit aus, keinen Score und kein Urteil darüber, wer einen Text geschrieben hat. Es sagt dir, welche Zeichen in deiner Zeichenkette stehen, wo sie stehen und was mit jedem einzelnen davon passiert.

Fragen

Warum nicht einfach jedes unsichtbare Zeichen entfernen?

Weil einige davon tragend sind. Wer jeden Verbinder ohne Breite entfernt, macht aus einem Familien-Emoji drei einzelne Personen und schreibt persische Wörter falsch. Wer jede Richtungsmarke entfernt, zerlegt das Layout von gemischtem Hebräisch und Deutsch. Ein Reiniger, der das stillschweigend tut, ist schlimmer als gar kein Reiniger, weil du nicht sehen kannst, was er mitgenommen hat.

Ist die Browser-Fassung dasselbe wie das Kommandozeilenwerkzeug?

Ja, und das wird geprüft. Golden Fixtures, erzeugt aus der Python-Implementierung, nageln die TypeScript-Engine auf Byte-identische Ausgabe fest, für jedes Fixture und jede Optionskombination; bei jeder Abweichung scheitert der Build.

Kann es mir sagen, ob eine KI einen Text geschrieben hat?

Nein, und es wird das nie behaupten. Unsichtbare Zeichen verraten dir, dass ein Text durch irgendeine Software gelaufen ist. Sie verraten nicht, durch welche, und sie verraten nicht, wer die Wörter geschrieben hat. Jedes Werkzeug, das aus einer Zeichenzahl eine Prozentangabe macht, erfindet diese Zahl.

Verlässt mein Text meinen Browser?

Nein. Es gibt keine serverseitige Verarbeitung und nichts, wohin sich etwas hochladen ließe: Die Engine ist JavaScript, das auf deinem Gerät läuft, und die Seite ist eine Sammlung statischer Dateien. Du kannst die Netzwerkverbindung trennen, und sie funktioniert weiter.

Anonyme Nutzungsstatistik?

Ghostchars verarbeitet deinen Text im Browser und lädt ihn nie hoch. Wir würden gern Seitenaufrufe zählen, und zwar über einen selbst gehosteten, cookie-armen Analyse-Endpunkt, der niemals Inhalte erfasst.