Como o Ghostchars decide o que remover
Uma passagem pelo texto, uma decisão por caractere e um motivo legível para cada decisão. Sem modelo, sem heurística, sem nota.
Quatro etapas
A limpeza roda numa ordem fixa. Só a terceira etapa olha para caracteres individuais; as duas primeiras existem para que a terceira encontre uma string previsível.
Normalizar (opcional)
Se você pediu, o texto é normalizado antes para NFC ou NFKC. É isso que derrota a esteganografia por forma de normalização, em que a mensagem é codificada na escolha entre "é" como um único ponto de código e "e" mais um acento combinante. O NFKC vai além e converte letras matemáticas estilizadas, ligaduras e formas de largura completa, o que é destrutivo, então vem desligado.
Unificar quebras de linha e sequências de espaço (opcional)
CRLF e CR viram LF; a barra de citação do terminal no começo de uma linha sai junto com o espaço depois dela; um travessão entre espaços vira um hífen entre espaços; espaços e tabulações no fim de cada linha são cortados; sequências de espaços dentro de uma linha viram um só. São reescritas de padrão sobre a string inteira, e não decisões caractere a caractere, e cada uma é uma opção separada porque cada uma altera texto que você consegue ver.
Classificar cada caractere
Cada caractere é procurado nas tabelas do mecanismo numa ordem fixa, e a primeira correspondência vence. O resultado é uma categoria, a opção que a governa e o que deve entrar no lugar: uma string vazia para "apagar", um espaço comum para um espaço exótico, uma letra ASCII para uma letra parecida. Os caracteres comuns não correspondem a nada e voltam intocados.
Remontar, com contexto
A saída é montada caractere por caractere. Um caractere cuja opção está desligada é copiado adiante e relatado como encontrado, mas não alterado. Um caractere cuja opção está ligada é removido ou substituído. O tempo todo, o mecanismo lembra do último caractere que de fato MANTEVE, não do último que viu, porque é essa a base à qual um ligador ou um seletor de variação se prende, e um caractere de ligação não muda essa base.
As quatro passagens são sobre caracteres; nenhuma delas lê uma frase. Se a escrita repete os hábitos que as pessoas notam, frases todas do mesmo tamanho, transições prontas, travessões em série, isso é outra medição: o relatório de hábitos de escrita conta esses hábitos e entrega os números, sem nota e sem probabilidade.
A ordem das decisões
As categorias são testadas nesta ordem, e a primeira correspondência decide. A ordem importa: um espaço de largura zero é testado antes da regra genérica de controle de formatação, então ele é relatado como caractere de largura zero e não como um "Cf" anônimo.
| # | Por categoria | Governado por |
|---|---|---|
| 1 | Largura zero e invisíveis | Limpeza padrão (sempre ativo) |
| 2 | Substituições bidirecionais | Limpeza padrão (sempre ativo) |
| 3 | Marcas bidirecionais | Marcas bidi --bidi |
| 4 | Ligadores, seletores e preenchedores | Agressivo -a |
| 5 | Qualquer outro controle de formatação | Limpeza padrão (sempre ativo) |
| 6 | Caracteres de uso privado | Caracteres de uso privado --pua |
| 7 | Espaços exóticos | Espaços -s |
| 8 | Separadores de linha | Quebras de linha -n |
| 9 | Caracteres de controle | Caracteres de controle --controls |
| 10 | Pontuação tipográfica | Pontuação --punct |
| 11 | Barras de citação do terminal | Barras de citação --gutters |
| 12 | Letras parecidas | Letras parecidas --confusables |
Duas exceções ficam dentro dessa ordem. O árabe, o siríaco e o kaithi têm controles de formatação que são ortografia comum, como os sinais numéricos árabes e o sinal numérico kaithi, e esses são reconhecidos antes da regra genérica de controle de formatação e deixados em paz. Tabulação, avanço de linha e retorno de carro ficam de fora do grupo dos caracteres de controle, porque apagá-los grudaria linhas.
Por que o contexto decide
Quatro famílias de caractere são invisíveis E essenciais: ligadores, seletores de variação, preenchedores específicos de certas escritas e marcas bidirecionais. O mesmo ponto de código pode ser contrabando numa posição e a grafia correta em outra, então o mecanismo olha para os vizinhos antes de decidir. É a parte que a maioria dos limpadores erra, nos dois sentidos: ou deixam passar os escondidos, ou quebram os de verdade.
O emoji de família
Um emoji de família são três ou quatro pessoas soldadas por ligadores de largura zero. Remova-os e um emoji vira três. O Ghostchars mantém um ligador cujos vizinhos dos dois lados são bases de emoji, e remove o ponto de código idêntico quando ele está entre duas letras latinas, onde não une nada e se esconde bem.
A palavra persa
O persa escreve as formas contínuas e o plural com um não-ligador de largura zero: não é enfeite, é a grafia. O Ghostchars mantém um ligador ou não-ligador quando os caracteres dos dois lados pertencem à mesma escrita conectada, seja árabe, persa, devanágari ou malaiala, e o remove em todo o resto. A mesma regra protege as vogais khmer, os seletores mongóis e os preenchedores de jamo do hangul ao lado das suas próprias escritas.
A bandeira da Escócia
A bandeira de uma subdivisão é uma bandeira preta seguida de caracteres de tag que soletram um código de região e uma tag de cancelamento. Esses mesmos caracteres de tag, soltos, são o ataque de ASCII smuggling: instruções invisíveis que um modelo de linguagem vai ler. O Ghostchars analisa a sequência inteira: os caracteres de tag dentro de uma bandeira completa são mantidos, e todo caractere de tag fora de uma é removido.
A frase em hebraico
As marcas de esquerda para a direita e de direita para a esquerda são o que mantém a pontuação do lado certo num texto de direção mista. O Ghostchars mantém uma marca bidi cujo vizinho é um caractere da direita para a esquerda e remove a que está à deriva num parágrafo em inglês, onde a única função dela é carregar um bit. Os cinco controles de substituição e de incorporação não recebem esse benefício da dúvida: eles conseguem reordenar um texto que você não escreveu, então saem sempre.
Cada uma dessas decisões é relatada. Um caractere mantido aparece como "mantido, essencial", junto da opção que o removeria mesmo assim, então nada é decidido em silêncio e nada fica escondido de você.
Verificado contra onze esquemas publicados
A ferramenta de linha de comando traz um teste adversarial que implementa onze técnicas documentadas de ocultação em texto como pares de codificador e decodificador. Para cada uma, ela embute um segredo, limpa o texto e confere duas coisas: que o decodificador não consegue mais recuperar o segredo, e que o texto de cobertura visível sobreviveu byte a byte idêntico.
| Esquema | Derrotado com |
|---|---|
| Zero-width binary (ZWSP / ZWNJ / ZWJ) | limpeza padrão |
| Word-joiner and invisible-maths binary | limpeza padrão |
| Tag-block ASCII smuggling | limpeza padrão |
| Variation-selector byte smuggling | limpeza padrão |
| Bidi mark binary (LRM / RLM) | limpeza padrão |
| SNOW trailing whitespace | Espaços no fim da linha --trailing |
| Cyrillic homoglyph binary | Letras parecidas --confusables |
| NFC / NFD normalisation-form binary | Normalizar (NFC) --nfc |
| NBSP / space homoglyph binary | Espaços -s |
| Single / double space binary | Juntar espaços --collapse |
| Curly / straight apostrophe binary | Pontuação --punct |
Cinco dos onze caem só com a limpeza padrão. Os outros seis precisam de uma opção, porque o portador é um caractere que você consegue ver, como um espaço, um apóstrofo ou uma letra parecida, e removê-lo sem ter sido pedido mudaria o texto.
O mesmo mecanismo em dois lugares
O mecanismo do navegador é uma porta da ferramenta de linha de comando em Python, não uma reinterpretação dela.
Um gerador roda a implementação em Python sobre um corpus de fixtures (todo ponto de código listado, todo caso de contexto, as onze cargas de esteganografia, prosa realista e misturada) para catorze combinações de opções, e registra a saída limpa e cada ocorrência. O mecanismo em TypeScript é testado contra esse registro. Qualquer divergência entre os dois quebra o build, então "o site faz o que o CLI faz" é resultado de teste, e não promessa.
Isso importa por uma razão prática: você pode conferir um arquivo num hook de pre-commit com o CLI e obter exatamente o resultado que o site teria dado.
O mesmo mecanismo roda em um terceiro lugar. O relatório do documento descompacta um .docx ou um .odt e lê o XML de dentro dele em vez de uma cadeia colada, então um caractere recebe dentro do arquivo o mesmo veredito que recebe no campo da página inicial, e os trechos ocultos, as alterações controladas e os metadados em volta são relatados sem serem apagados.
O que isto não consegue fazer
Tudo acima é sobre caracteres. Estas quatro coisas não são sobre caracteres, e nenhuma limpeza mexe nelas.
- Marcas d'água estatísticas: Esquemas como o SynthID-Text e o método da "lista verde" de Kirchenbauer enviesam QUAIS palavras um modelo escolhe. O sinal vive na escolha das palavras ao longo de um trecho inteiro, não em caractere nenhum. Só reescrever o texto muda isso, e esta ferramenta não reescreve texto.
- Detecção estilométrica: Detectores como o GPTZero pontuam o quanto cada palavra é previsível e o quanto essa previsibilidade varia. Remover um espaço de largura zero não move essa pontuação nem um ponto. O relatório de hábitos de escrita mede os hábitos editáveis que estão por trás dela (variação no tamanho das frases, palavras-sinal, transições prontas) e ainda assim se recusa a emitir uma probabilidade.
- Ocultação no nível do formato: Texto branco sobre branco, trechos ocultos, comentários de HTML e metadados de PDF se escondem FORA do texto. A ferramenta de documentos relata tudo isso (trechos ocultos, alterações controladas, comentários, identificadores de sessão de edição, metadados de autoria), mas de propósito não apaga nada, porque apagar muda o documento e essa decisão deve ser sua.
- Canais de menos de um bit por emoji: Uma corrente de ligadores entre emojis, e um seletor de variação depois de um deles, são mantidos por padrão porque é assim que emoji funciona. Alguém determinado ainda consegue esconder uma pequena quantidade de dados ali. Ligue o Agressivo se você precisa que eles sumam, e aceite que os seus emojis vão se desmanchar.
O Ghostchars não emite probabilidade, nota nem veredito sobre quem escreveu um texto. Ele diz quais caracteres estão na sua string, onde eles estão e o que acontece com cada um.
Perguntas
Por que não remover simplesmente todo caractere invisível?
Porque vários deles são essenciais. Remover todo ligador de largura zero transforma um emoji de família em três pessoas separadas e escreve palavras persas erradas. Remover toda marca bidi quebra o layout de textos que misturam hebraico e inglês. Um limpador que faz isso caladinho é pior do que limpador nenhum, porque você não consegue ver o que ele levou.
A versão do navegador é igual à ferramenta de linha de comando?
É, e isso é testado. Fixtures de referência geradas a partir da implementação em Python prendem o mecanismo em TypeScript a uma saída byte a byte idêntica para cada fixture e cada combinação de opções; o build falha em qualquer diferença.
Ela consegue me dizer se um texto foi escrito por IA?
Não, e nunca vai dizer que consegue. Caracteres ocultos contam que um texto passou por algum software. Eles não contam por qual software, e não contam quem escreveu as palavras. Qualquer ferramenta que transforme uma contagem de caracteres em porcentagem de confiança está inventando o número.
Meu texto sai do meu navegador?
Não. Não existe processamento no servidor nem para onde enviar: o mecanismo é JavaScript rodando no seu dispositivo, e o site é um conjunto de arquivos estáticos. Você pode se desconectar da rede e ele continua funcionando.