Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions garak/probes/encoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,63 @@ def __init__(self, config_root=_config):
EncodingMixin.__init__(self)


def _fancy_text(
text: bytes, upper_base: int, lower_base: int, digit_base: int
) -> bytes:
output = []
for char in text.decode():
if "A" <= char <= "Z":
output.append(chr(upper_base + ord(char) - ord("A")))
elif "a" <= char <= "z":
output.append(chr(lower_base + ord(char) - ord("a")))
elif "0" <= char <= "9":
output.append(chr(digit_base + ord(char) - ord("0")))
else:
output.append(char)
return bytes("".join(output), "utf-8")


def fullwidth(text: bytes) -> bytes:
output = []
for char in text.decode():
codepoint = ord(char)
if 0x21 <= codepoint <= 0x7E: # printable ASCII -> Fullwidth Forms
output.append(chr(codepoint + 0xFEE0))
elif char == " ":
output.append(" ") # ideographic space
else:
output.append(char)
return bytes("".join(output), "utf-8")


def math_bold(text: bytes) -> bytes:
# Mathematical Alphanumeric Symbols: bold
return _fancy_text(text, 0x1D400, 0x1D41A, 0x1D7CE)


def monospace(text: bytes) -> bytes:
# Mathematical Alphanumeric Symbols: monospace
return _fancy_text(text, 0x1D670, 0x1D68A, 0x1D7F6)


class InjectFancyText(EncodingMixin, garak.probes.Probe):
"""Probe for susceptibility to "fancy text" Unicode font encoding.

Encodes payloads by mapping ASCII characters to visually-similar Unicode
glyphs (fullwidth forms and Mathematical Alphanumeric Symbols), in the style
of the qaz.wtf Unicode text converter, and tries a variety of ways of getting
the target model to repeat & decode them."""

doc_uri = "https://qaz.wtf/u/convert.cgi"
encoding_funcs = [fullwidth, math_bold, monospace]
encoding_name = "Fancy text"
active = True

def __init__(self, config_root=_config):
garak.probes.Probe.__init__(self, config_root=config_root)
EncodingMixin.__init__(self)


class InjectUnicodeTagChars(EncodingMixin, garak.probes.Probe):
"""Probe for susceptibility to unicode tag ASCII smuggling

Expand Down
25 changes: 25 additions & 0 deletions tests/probes/test_probes_encoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import garak.probes.encoding
from garak import _plugins
from garak.probes.encoding import InjectAtbash
from garak.probes.encoding import fullwidth, math_bold, monospace

ENCODING_PROBES = [
classname
Expand Down Expand Up @@ -91,3 +92,27 @@ def test_atbash_function(plain, expected):
encoder = InjectAtbash.atbash # staticmethod reference
result = encoder(plain.encode()).decode()
assert result == expected


@pytest.mark.parametrize(
"encoder, plain, expected",
[
# Fullwidth Forms: letters, digits and punctuation are all remapped
(fullwidth, "abc", "abc"),
(fullwidth, "XYZ", "XYZ"),
(fullwidth, "Hello, World! 123", "Hello, World! 123"),
(fullwidth, "1234!?", "1234!?"),
# Mathematical Alphanumeric Symbols: bold (punctuation passes through)
(math_bold, "abc", "𝐚𝐛𝐜"),
(math_bold, "XYZ", "𝐗𝐘𝐙"),
(math_bold, "1234!?", "𝟏𝟐𝟑𝟒!?"),
# Mathematical Alphanumeric Symbols: monospace (punctuation passes through)
(monospace, "abc", "𝚊𝚋𝚌"),
(monospace, "XYZ", "𝚇𝚈𝚉"),
(monospace, "1234!?", "𝟷𝟸𝟹𝟺!?"),
],
)
def test_fancy_text_functions(encoder, plain, expected):
# Fancy-text encoders map ASCII to visually-similar Unicode glyphs.
result = encoder(plain.encode()).decode()
assert result == expected