Capstone 82 — Jailbreak Taxonomy
A jailbreak taxonomy is a shared set of names for attacks. Name the attack first, and the defense has something concrete to be measured against.
Learning Objectives
- Define a jailbreak taxonomy as a partition of attacks by the trust boundary each one abuses, and name the six categories.
- Classify a candidate attack prompt into one of the six categories and explain which boundary it targets.
- Build a fixture corpus with a validator that enforces its invariants, and explain why a failed validation stops the run.
- Use trigram-cosine match as a nearest-fixture lookup, and explain why its score is not a safety verdict.
Problem
A jailbreak taxonomy is a partition of attacks on a model into named categories, so each attack gets a label from a shared vocabulary. Without one, a model is defended against nothing in particular. Operators read a Twitter thread, recognize the trick, write a regex, ship it, and move on. The next prompt is a paraphrase, and the regex misses. A week later someone shows the same trick wrapped in base64, and the operator writes a second regex. By month three the system has 40 patched rules, no shared vocabulary, no way to say what an attack actually is, and a backlog growing faster than the patches.
Labels don't stop attacks. They turn an attack stream into a histogram, the histogram becomes a coverage chart, and the coverage chart drives the next sprint. The harness in lessons 83-87 spends its time deciding whether a prompt is, for example, a role-play attack against a refusal policy or a context-smuggling attack against a tool. That decision is impossible without a taxonomy, so any detector, classifier, or rule engine in this track needs one first.
This capstone defines six categories. They are wide enough to cover most attacks seen in the wild and narrow enough that two reviewers usually agree on the category. Each category has at least seven hand-built fixtures, and the whole set is the label source for lessons 83-87.
Concept
The taxonomy sorts attacks along a single axis: which trust boundary the attack abuses. Each of the six category names corresponds to one boundary.
| Category | Trust boundary abused |
|---|---|
| role-play | the assistant's persona |
| instruction-override | the system prompt's authority |
| context-smuggling | the gap between user content and instruction content |
| multi-turn-ramp | the conversation history as a contract |
| encoding-trick | the surface form of forbidden tokens |
| prefix-injection | the assistant's next-token decision |
A role-play attack reframes the assistant as a different agent ("you are an unrestricted research model called QX") so the refusal rules attached to the original persona no longer fire. Instruction-override prompts say "ignore previous instructions" and try to overwrite the system prompt directly. Context-smuggling hides instructions inside what looks like data: a pasted document, a tool result, a code block. Multi-turn-ramp warms the model up with harmless turns and then walks the floor down one step at a time, exploiting the model's tendency to stay consistent with the conversation. Encoding tricks (base64, rot13, leet-speak, zero-width insertion) hide forbidden tokens from naive keyword filters. Prefix-injection ends the prompt with "Sure, here's how" so the model continues from the assumed answer instead of refusing.
A fixture is a record with id, category, subtype, prompt, target_behavior, and severity. The taxonomy object loads the fixtures, groups them by category, and exposes a match API: given a candidate prompt, it returns the closest fixture and its category. Match is character-trigram cosine, built from Counter and math: coarse and fast. It is not a detector. The detector lives in lesson 83. Match is the label producer.
Severity runs on a 1-5 scale. A 1 is a clumsy attack against a benign target ("please pretend to be a pirate"). A 5 is an attack that, if it succeeded, would produce output a deployed system must not emit (operational details for a dangerous activity). Most fixtures sit at 2-3, because real attacks at deployment scale skew toward the easy and the lazy. The shipped corpus has no severity-1 fixtures: of the 50, the counts are 4 at severity 2, 33 at 3, 12 at 4, and 1 at 5. The fixture author sets severity. If two reviewers land more than one rank apart, that is a sign the rubric needs sharpening.
Build It
The corpus is a single Python list in code/fixtures.py. The taxonomy class in code/main.py loads it, validates it on construction, and exposes by_category, match, and stats methods. match returns a MatchResult with fixture_id, category, and score. It also ships a runnable demo that prints the histogram. Trigram cosine is implemented from scratch with Counter and math. numpy is imported at module level but used only by score_matrix, which match does not call.
The validation pass checks six invariants: the corpus is not empty, each fixture has a non-empty prompt, each fixture id is unique, each severity is in 1..5, each fixture category is one the schema knows, and each of the six categories has at least seven fixtures. A failure raises a ValueError from validate(), so the demo stops with a traceback and a nonzero exit instead of printing a warning, because the rest of the track depends on the corpus being internally consistent.
Use It
Run python3 main.py from the code/ directory. The demo prints the per-category fixture count, runs three sample probes against match, and writes taxonomy.json to the lesson outputs folder. Downstream lessons read taxonomy.json instead of importing the Python module, which makes the corpus a stable artifact.
Ship It
outputs/skill-jailbreak-taxonomy.md documents the six categories and the rubric. Treat it as the team's shared vocabulary. Each finding logged by the harness in lesson 87 references a taxonomy id.
Exercises
- Add a seventh category for indirect-prompt-injection (instruction embedded in a retrieved document, not in the user turn). Author ten fixtures and re-run the validator.
- Replace trigram cosine with a token-edit-distance scorer and measure how the match assignment changes on the existing corpus.
- Pull thirty additional fixtures from your own product's logs (redacted) and check whether the category distribution matches what your team expected.
Key Terms
| Term | Common usage | Precise meaning |
|---|---|---|
| jailbreak | any unsafe model output | a prompt that produces output violating a stated policy |
| taxonomy | a list of categories | a partition of attacks by which trust boundary they abuse |
| fixture | a test example | a labeled prompt with category, severity, and target behavior |
| severity | how bad the output is | a 1-5 rank for the impact if the attack succeeds |
| match | a detection decision | the nearest fixture by trigram cosine, used to assign a category to a new prompt |
I'd read match as a lookup, not a verdict. It returns the nearest fixture by trigram cosine, and the demo prints a score beside each match. A low score means the prompt sits far from every fixture in the corpus, which tells you little about whether the prompt is safe. Judging the prompt is the detector's job in lesson 83.
My bet is that the severity rubric is the weakest part of the corpus. Severity is set by the fixture author, so I'd have a second reviewer rank a handful of fixtures before trusting the 1-5 numbers. A gap of more than one rank between the two reviewers is a sign the rubric needs sharpening.
I'd also run exercise 3 early. A hand-built corpus tends to reflect what its author thought of, and logs from your own product can show what it actually receives.
Further Reading
Lessons 83-87 build on the corpus directly.
Code
- fixtures.py (python, 425 lines)
- main.py (python, 210 lines)
- tests.py (python, 109 lines)