Metadata-Version: 2.4
Name: casefold-fuzz
Version: 0.1.0
Summary: Prompt injection mutation and homoglyph jailbreak fuzzer: a pure-stdlib generator that expands one injection string into casefold, confusable, invisible-character, leetspeak and spacing evasion variants to stress-test guardrails you own.
Author-email: Fevzi Ege Yurtsevenler <egeyurtsevenler@gmail.com>
License: Apache-2.0
Project-URL: Homepage, https://github.com/fevziegeyurtsevenler/casefold-fuzz
Project-URL: Source, https://github.com/fevziegeyurtsevenler/casefold-fuzz
Project-URL: Issues, https://github.com/fevziegeyurtsevenler/casefold-fuzz/issues
Project-URL: prompt-canon (defense pair), https://pypi.org/project/prompt-canon/
Keywords: prompt injection mutation,homoglyph jailbreak fuzzer,casefold evasion generator,guardrail testing,unicode confusables,zero-width smuggling,llm security,red team,owasp llm top 10,mitre atlas
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security
Classifier: Topic :: Text Processing :: Filters
Classifier: Typing :: Typed
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"
Dynamic: license-file

# 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**](https://pypi.org/project/prompt-canon/): *fuzz generates
> the variants that canon normalizes away.* Use `casefold-fuzz` to attack your
> test harness and `prompt-canon` to 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

```bash
pip install casefold-fuzz
```

Or from source:

```bash
git clone https://github.com/fevziegeyurtsevenler/casefold-fuzz
cd casefold-fuzz
pip install -e ".[test]"
```

---

## Quickstart

```python
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

```python
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:

```python
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`.
- **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
    **survive** `map_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 `NOTICE` for
  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](https://genai.owasp.org/) 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

```bash
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**](https://pypi.org/project/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

Apache-2.0. See [`LICENSE`](LICENSE) and [`NOTICE`](NOTICE).
