Tokenization and filter bypass

    A moderation filter that scans for a banned word is checking the wrong layer. The model never sees words — it sees tokens, and the mapping between the two is easy to disturb.

    By Piotr Zenon Żabrowski · Published · Updated

    The filter checks strings, the model reads tokens

    Tokenization splits input text into vocabulary units before anything else happens — a whole word, a fragment, punctuation, or a single character, depending on how the tokenizer's algorithm (commonly Byte Pair Encoding) merged character pairs during training. A word-matching filter checks the raw string. Tokenization itself is deterministic: identical characters, through the same tokenizer and pipeline, always produce the same token sequence. What breaks a filter is that two inputs which look identical to a human are not always the same characters underneath — a lookalike code point, an inserted formatting character, or a different script can sit behind text that reads the same on screen, and each variant tokenizes on its own terms.

    What manipulation changes the outcome

    • Unusual spacing or inserted characters (zero-width characters, extra punctuation) push a word's split point somewhere the filter's pattern doesn't anticipate.
    • Homoglyphs, full-width Unicode variants, and mixed scripts substitute different code points for characters that render the same way, so the visible word survives but the token sequence behind it does not match what the filter was written to catch.
    • Switching to a language the moderation layer was tuned less carefully for changes the tokenization and the coverage a security team can vouch for.

    A second, quieter cost: tokenization is not language-neutral

    Token count depends on how well a tokenizer's learned vocabulary fits a language's morphology, not on meaning alone. If the training corpus behind the merge rules is mostly English, the vocabulary ends up with whole-word entries for common English forms, while heavily inflected languages get cut into smaller subword pieces to cover endings the vocabulary never learned as single units. Two sentences with the same meaning and word count can land at different token counts once tokenized. That is not a filter bypass, but a budgeting problem: usage-based pricing charges per token on input and output regardless of answer quality, and a context window or rate limit sized on English-language testing leaves less real headroom for text that tokenizes less efficiently.

    What holds up

    Design for tokens, not strings

    • Run moderation on model output and on normalized, decoded text — not just the raw pre-tokenization string.
    • Normalize Unicode (NFKC) before any string-based filter runs, and handle zero-width and control characters through an explicit, language-aware policy rather than stripping them all — some are load-bearing in scripts using joiners or combining marks. Test the policy against real multilingual samples before rollout.
    • Treat keyword and regex filters as a noise reducer, not a security boundary — pair them with a classifier that scores meaning, not surface form.
    • Track per-language token cost separately when budgeting or rate-limiting; a flat per-request token cap is not language-neutral.

    Frequently asked questions

    Does an invisible edit to a string really change what the model reads?

    Only if it changes what's actually there. Tokenization is deterministic for a given input and tokenizer — feed it the same code points twice and it splits them the same way twice. What an attacker changes is the input itself: swapping in a homoglyph, a full-width character, or a zero-width joiner produces a string that looks the same on screen but is a different sequence of code points, and that different sequence tokenizes differently. The filter and the model can end up disagreeing about what was said because they were, technically, shown different text.

    Is Unicode normalization enough to stop these bypasses?

    It closes the cheapest version of the trick, not the category. Normalization should run before any string-based check, but treat it as one layer, not the fix — the underlying issue is that a filter matches strings while the model reasons over tokens, which means output-side and meaning-based checks still matter more than input pattern-matching.