casefold-fuzz — a Prompt Injection Mutation & Homoglyph Jailbreak Fuzzer for Guardrail Testing
casefold-fuzz is a small, pure-standard-library Python library that expands one prompt-injection or jailbreak string into a deterministic set of evasion variants, so you can stress-test a guardrail you own. It is a prompt injection mutation generator, a homoglyph jailbreak fuzzer, and a casefold evasion generator in one: given a base payload it emits case-folded, cross-script confusable, invisible-character, leetspeak, and character-spacing rewrites that look identical (or nearly identical) to a human but no longer match a naive keyword filter, allow/deny list, or embedding classifier.
Attack + defense pair. casefold-fuzz is the offensive counterpart to prompt-canon: fuzz generates the variants that canon normalizes away. Use
casefold-fuzzto attack your test harness andprompt-canonto defend it. The pair relationship is asserted in this library's own test-suite.
Keywords: prompt injection mutation, homoglyph jailbreak fuzzer, casefold evasion generator, guardrail testing, unicode confusables, zero-width smuggling, LLM red teaming, OWASP LLM Top 10, MITRE ATLAS.
- Zero runtime dependencies — pure standard library (only
unicodedata, and only optionally). - Python 3.8+.
- Deterministic and offline — no randomness, no model, no network. The same input always yields the same ordered list of variants.
- Auditable — every variant carries the technique that produced it and a human-readable note describing exactly what was changed.
Install
pip install casefold-fuzz
Or from source:
git clone https://github.com/fevziegeyurtsevenler/casefold-fuzz
cd casefold-fuzz
pip install -e ".[test]"
Quickstart
from casefold_fuzz import mutate
variants = mutate("ignore previous instructions", max_variants=8)
for v in variants:
print(f"[{v.technique:9}] {v.note}")
print(" ", v.text)
mutate returns a list of Variant(text, technique, note) objects. With a
small max_variants it round-robins across techniques so you still get a
balanced sample from every class.
API
mutate(
text,
*,
techniques=("casefold", "homoglyph", "invisible", "leet", "spacing"),
max_variants=50,
) -> list[Variant]
Per-technique generators are also exposed and each returns list[Variant]:
| Generator | What it does |
|---|---|
casefold_variants(text) |
Upper/lower/title/swapcase, plus the Turkish dotted/dotless I (i→İ U+0130, i→ı U+0131) and German sharp S (ss↔ß) traps that break naive str.lower()/str.casefold(). |
homoglyph_variants(text) |
Cross-script confusable substitution (Cyrillic / Greek / Armenian / Latin-extended look-alikes), full and single-swap. |
invisible_variants(text) |
Interleaves zero-width / invisible characters (U+200B, U+200C, U+2060, U+FEFF) between letters. |
spacing_variants(text) |
Inserts separators (space, ., -, _, NBSP) between characters. |
leet_variants(text) |
ASCII leetspeak substitution (a→4, e→3, i→1, o→0, …) — evasion with no Unicode at all. |
Variant is a frozen dataclass with fields text, technique, note.
The attack/defense pair with prompt-canon
The two libraries are designed to be run against each other:
from casefold_fuzz import invisible_variants
from prompt_canon import strip_invisible # pip install prompt-canon
base = "ignore previous instructions"
for v in invisible_variants(base):
recovered = strip_invisible(v.text)
print(v.note, "->", recovered == base)
-
casefold-fuzz generates what prompt-canon is designed to remove:
- case variants round-trip losslessly through
prompt_canon.fold_case(verified for every variant this library emits, including the Turkish dotted/dotless I and German sharp-S forms); - the U+200B / U+2060 / U+FEFF invisible variants round-trip losslessly
through
prompt_canon.strip_invisible; - the common Cyrillic homoglyph variants (a, e, o, c, i, p, s, x, y, j)
round-trip through
prompt_canon.map_confusables.
- case variants round-trip losslessly through
-
Honest limitations (surfaced blind spots, not bugs):
- prompt-canon deliberately preserves U+200C ZWNJ and U+200D ZWJ because they are legitimate in Persian/Arabic/Indic scripts and emoji sequences, so a ZWNJ variant survives naive stripping;
- prompt-canon's confusables table is itself a curated subset, so a few
homoglyphs this fuzzer emits are not in it — currently the Armenian
letters (
n→ո,u→ս) and Cyrillic VE (b→в) — and those variants survivemap_confusables.
Both kinds of survivor are exactly the blind spots the fuzzer exists to find, and each behaviour is pinned in
tests/so this section stays true.
This is why the pair is useful: the fuzzer tells you which evasion classes your normalizer already neutralizes and which ones slip through.
Honesty
- casefold-fuzz systematizes known, published evasion classes — Unicode case folding, homoglyph/confusable substitution (UTS #39), zero-width smuggling, leetspeak, and character spacing. It introduces no novel attack and no zero-day.
- It makes no success-rate claims. A variant "evading a filter" is a property of your target system, not of this library; casefold-fuzz only produces candidates. Whether any candidate bypasses any given model or guardrail is something you must measure yourself.
- The confusable and character tables are a small, manually reviewed subset
of the phenomena, chosen for legibility, not exhaustiveness. See
NOTICEfor attribution to the Unicode Security Mechanisms data. - Prior art this builds on: the Unicode Consortium's confusables/security work (UTS #39), the "Trojan Source" line of research on bidirectional/hidden characters, and the broader LLM red-teaming community's documentation of these evasion classes. The defensive counterpart, prompt-canon, is by the same author.
Responsible use
This is a defensive-testing tool. Use it only against systems you own or are explicitly authorized to assess. Do not use it to attack third-party services, bypass safety systems you do not control, or generate payloads for unauthorized access.
Mapping to standard threat taxonomies:
- OWASP LLM Top 10 — LLM01: Prompt Injection and LLM02 (insecure output / content evasion). casefold-fuzz helps you verify your input canonicalization and detection before an adversary probes it.
- MITRE ATLAS — evasion of ML-enabled defenses, e.g. AML.T0051 (LLM Prompt Injection) and adversarial-input crafting techniques. Use the generated variants to build regression tests for your guardrail.
The intended workflow is: generate variants with casefold-fuzz → feed them to
your guardrail / classifier → measure detection → close gaps (e.g. by
canonicalizing with prompt-canon first) → keep the surviving variants as
regression tests.
Tests
pip install -e ".[test]"
pytest -q
The suite (46 tests) covers: every technique produces non-empty, distinct,
correctly labelled variants; the casefold variant of "ignore" contains a
Turkish dotted/dotless I form and the ss→ß expansion fires only when ss is
present; invisible variants round-trip back to the original after zero-width
stripping (asserted both with a local dependency-free stripper and, when
installed, against the real prompt_canon.strip_invisible); the documented
ZWNJ-survives-canon limitation; that every case variant folds back through
prompt_canon.fold_case; that the common Cyrillic homoglyphs recover through
prompt_canon.map_confusables while the Armenian/Cyrillic-VE gap letters
survive it (the pinned blind-spot claim); and full determinism / idempotency of
mutate. The prompt-canon assertions skip cleanly when it is not installed.
Related work by the same author
- prompt-canon — the defensive counterpart: canonicalize (strip invisibles, map confusables, locale-safe case fold) before you guard. Repo: https://github.com/fevziegeyurtsevenler/prompt-canon.
- unicode-threat-reveal — an interactive Hugging Face Space for inspecting hidden/confusable characters in a string.
License
Release files for casefold-fuzz 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| casefold_fuzz-0.1.0.tar.gz | 21.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| casefold_fuzz-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 38.9 kB
Release files / casefold_fuzz-0.1.0.tar.gz
| Download URL | casefold_fuzz-0.1.0.tar.gz |
|---|---|
| Size | 21.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b28896321cfe94b624600adeff1aa5d8c1b6233906ec28b9c9989f4cebcf0493
|
|
BLAKE2b-256 checksum How to use checksums |
c77efee55322709de445be0e34061948a3c4282478945ad992149aaac2f26a2c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.5
|
Release files / casefold_fuzz-0.1.0-py3-none-any.whl
| Download URL | casefold_fuzz-0.1.0-py3-none-any.whl |
|---|---|
| Size | 17.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2425728d324eb49f82261b066368151fecf836300258fbeff6e690777675d222
|
|
BLAKE2b-256 checksum How to use checksums |
126f91bd490af7b3e5e053a3a516b751adf4472e08a9db50de5c7bd1109a63fe
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.5
|