You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
AsciiSmugglerConverter silently removes every character outside the ASCII printable range 0x20-0x7E. The dropped characters go to logger.error and nowhere else, so convert_async returns a ConverterResult that reports success while carrying a payload that is not the prompt.
café encodes to a payload that decodes back to caf. 你好世界 encodes to an empty payload, or to nothing but the two control tags when unicode_tags=True. An attack run against a non-English objective then scores as "attack did not succeed" against a target that was never sent the objective.
The two sibling converters in the same package do not have this problem. SneakyBitsSmugglerConverter and VariationSelectorSmugglerConverter both encode the UTF-8 bytes and round-trip Unicode losslessly.
There is a second, smaller problem in decode_message. The report of whether anything was hidden compares the decoded length to the input length:
Those two lengths are equal exactly when every character was a hidden tag, so decoding this converter's own unicode_tags=False output logs No hidden Unicode Tag characters discovered. for a string that is entirely hidden tags.
Steps/Code to Reproduce
importasynciofrompyrit.converterimportAsciiSmugglerConverter, SneakyBitsSmugglerConverterasyncdefmain():
forclsin (AsciiSmugglerConverter, SneakyBitsSmugglerConverter):
forpromptin ["café", "你好世界"]:
encoded=awaitcls(action="encode").convert_async(prompt=prompt, input_type="text")
decoded=awaitcls(action="decode").convert_async(prompt=encoded.output_text, input_type="text")
print(cls.__name__, repr(prompt), "->", repr(decoded.output_text))
# Second problem: decoding a fully hidden message reports that nothing was hidden.encoded=awaitAsciiSmugglerConverter(action="encode", unicode_tags=False).convert_async(
prompt="Hello", input_type="text"
)
awaitAsciiSmugglerConverter(action="decode").convert_async(prompt=encoded.output_text, input_type="text")
asyncio.run(main())
Expected Results
Round-trip through AsciiSmugglerConverter returns the prompt it was given, as it does for the other two smuggling converters.
If Unicode Tags cannot represent a character, the converter should say so through the result rather than dropping the character and returning success.
decode_message should report that hidden tags were found when they were found.
This is the same class of defect as #2308 (BrailleConverter) and #2383 (AcrosticConverter), both of which were fixed by preserving the input rather than dropping it. The Unicode Tags block only has code points for 0x20-0x7E, so pass-through is not available here in the same form, and the fix needs a decision on which of these you want:
Encode the UTF-8 bytes, as SneakyBitsSmugglerConverter already does. Lossless, but changes the encoding for existing ASCII payloads unless it is opt-in.
Raise on unrepresentable input. Lossless in the sense that nothing is silently wrong, and it is the smallest change.
Happy to open the PR for whichever you prefer. Existing coverage in tests/unit/converter/test_ascii_smuggler_converter.py only passes hi and hello, so there is no non-ASCII case in the suite today.
Duplicate check performed on 2026-09-02: searched open and closed issues and pull requests for AsciiSmuggler, smuggler, unicode tags, dropped characters, silently drops, and non-ASCII, and read the titles of all 50 open pull requests. #479, #827 and #842 built the converter and #1967 is a closed refactor. Nothing covers this.
Versions
OS: macOS 15.5
Python version: 3.12.11
PyRIT version: installed from main in editable mode at 6d5b1a9
Describe the bug
AsciiSmugglerConvertersilently removes every character outside the ASCII printable range0x20-0x7E. The dropped characters go tologger.errorand nowhere else, soconvert_asyncreturns aConverterResultthat reports success while carrying a payload that is not the prompt.caféencodes to a payload that decodes back tocaf.你好世界encodes to an empty payload, or to nothing but the two control tags whenunicode_tags=True. An attack run against a non-English objective then scores as "attack did not succeed" against a target that was never sent the objective.The two sibling converters in the same package do not have this problem.
SneakyBitsSmugglerConverterandVariationSelectorSmugglerConverterboth encode the UTF-8 bytes and round-trip Unicode losslessly.There is a second, smaller problem in
decode_message. The report of whether anything was hidden compares the decoded length to the input length:Those two lengths are equal exactly when every character was a hidden tag, so decoding this converter's own
unicode_tags=Falseoutput logsNo hidden Unicode Tag characters discovered.for a string that is entirely hidden tags.Steps/Code to Reproduce
Expected Results
Round-trip through
AsciiSmugglerConverterreturns the prompt it was given, as it does for the other two smuggling converters.If Unicode Tags cannot represent a character, the converter should say so through the result rather than dropping the character and returning success.
decode_messageshould report that hidden tags were found when they were found.Actual Results
Additional context
This is the same class of defect as #2308 (
BrailleConverter) and #2383 (AcrosticConverter), both of which were fixed by preserving the input rather than dropping it. The Unicode Tags block only has code points for0x20-0x7E, so pass-through is not available here in the same form, and the fix needs a decision on which of these you want:SneakyBitsSmugglerConverteralready does. Lossless, but changes the encoding for existing ASCII payloads unless it is opt-in.Happy to open the PR for whichever you prefer. Existing coverage in
tests/unit/converter/test_ascii_smuggler_converter.pyonly passeshiandhello, so there is no non-ASCII case in the suite today.Duplicate check performed on 2026-09-02: searched open and closed issues and pull requests for AsciiSmuggler, smuggler, unicode tags, dropped characters, silently drops, and non-ASCII, and read the titles of all 50 open pull requests. #479, #827 and #842 built the converter and #1967 is a closed refactor. Nothing covers this.
Versions