probes: detect ANSI escapes by tokenizer round trip, not vocab scan - #2079
probes: detect ANSI escapes by tokenizer round trip, not vocab scan#2079VishnuR23 wants to merge 1 commit into
Conversation
AnsiRawTokenizerHF walked the whole tokenizer vocabulary looking for entries containing a live ANSI payload as a substring. An escape sequence does not have to occupy a single vocabulary entry to be reachable, so this missed any payload split across tokens: against gpt2 the scan reports zero risky entries and the probe passes, while all four LIVE_PAYLOAD_TOKENS in fact survive an encode/decode round trip. Check the payload set directly instead, treating a sequence as risky when the tokenizer reproduces it intact. Special tokens are excluded from the round trip so they cannot corrupt the comparison. The control attempt now comes from a fixed benign sequence, since there is no longer a vocabulary walk to draw an arbitrary clean entry from. Adds tests for AnsiRawTokenizerHF, which previously had none. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Vishnu Rajeev <19866703+VishnuR23@users.noreply.github.com>
|
Follow-up on testing coverage, and one pre-existing thing I noticed while checking it. I ran the probe against two of the three entries in
I could not exercise This is not introduced here — |
Addresses #1376, implementing @erickgalinkin's suggestion from #1351 (comment).
What this changes
AnsiRawTokenizerHF.probe()walked the entire tokenizer vocabulary looking for entries that contain a live ANSI payload as a substring. As raised in the review discussion, an escape sequence does not need to occupy a single vocabulary entry to be reachable — it only needs to survive encoding and decoding. The substring scan therefore misses any payload that tokenizes to more than one id.This is a false negative, not just an inefficiency. On
gpt2, none of the fourLIVE_PAYLOAD_TOKENSis a single vocabulary entry, so the old scan finds nothing and the probe reports a clean pass — while all four sequences in fact round-trip through the tokenizer intact:The probe now checks the
LIVE_PAYLOAD_TOKENSset directly, treating a sequence as risky when the tokenizer reproduces it intact. The loop goes from O(vocab) to four checks.Two details worth flagging for review:
encode(..., add_special_tokens=False)/decode(..., skip_special_tokens=True). Without this, BOS/EOS lands in the comparison and every round trip fails, silently reporting every tokenizer as clean.BENIGN_CONTROL_SEQUENCE). This keeps the documentedscore = 1/(risky + 1)behaviour.AnsiRawTokenizerHFhad no test coverage, so this adds some, using a stub tokenizer that maps each representable sequence to two ids — so the multi-token case that motivated the issue is what is actually exercised.Not duplicating existing work
Checked before starting: #1376 has no linked PR and no prior comments, and no open PR touches
garak/probes/ansiescape.py. I commented on the issue to claim it before writing code.AI assistance disclosure
This PR was written with AI assistance (Claude). I reviewed every changed line, ran the tests and the live
gpt2run below myself, and produced the old-vs-new comparison above to confirm the behaviour change is real rather than assumed.Verification
garak --target_type huggingface --target_name gpt2 --spec "probes.ansiescape.AnsiRawTokenizerHF"1/(risky + 1)score. Before this change the same run reported a clean pass.python -m pytest tests/probes/test_probes_ansiescape.py— 9 passedpython -m pytest tests/probes/test_probes_ansiescape.py tests/test_docs.py tests/test_internal_structures.py— 694 passedencodeis never called with special tokens enabledTested on macOS, Python 3.13.7.
One note on the diff:
blackreformatted two pre-existing assertions intests/probes/test_probes_ansiescape.pythat I did not otherwise touch. The file was notblack-clean onmain; happy to drop those hunks if you would rather keep the diff to the new code only.