learn-ai-red-team-with-phoebe / Builder session 7 of 10
Learn AI Red Team with Phoebe · Builder track · Session 7 of 10

Build the suite: an untested class is unknown, not safe

Sessions b2 to b6 gave you the classes. This session turns them into a suite, and puts a board under it that does the accounting honestly. You will run a scripted six-rung engagement against Beacon's support bot and watch the moment that makes a red team both worth its budget and unpopular: a fortnight of testing that removes almost no risk and converts all of it from guessed to measured. Then you will drive the board yourself, find the minimum control set that reaches all green, and see what happens when you delete the one control every team reaches for first.

🟠 Builder track Security engineers · AI engineers · platform leads 45 min
0-3 · Welcome 3-14 · The accounting rule 14-36 · Build-along: the board 36-45 · Proxies, tools, Q&A
Part 0

The suite is an accounting problem before it is a testing problem

⚠ Authorization This session is about testing AI systems you own, or are contracted in writing to test. Everything here runs against Beacon, a fictional company, inside the rules-of-engagement document you wrote in b1. The board names attack classes so you can build test cases, and pairs each one with the defence that addresses it. It contains no exploit payloads and teaches no attack craft. Testing a system you are not authorized to test is a crime in most jurisdictions, and no framing in a suite manifest changes that.

Most teams build a suite by writing test cases for the classes they find interesting, running them, and reporting what came back. That is a reasonable way to spend a fortnight and a terrible way to produce a number, because it silently answers the wrong question. The report says "here is what we found". The board has to say "here is what we found, and here is what we never looked at, carried at full risk". Those two documents are the same length and mean completely different things.

Live - presented in session Self-study - read after class Official sources covered
★ What you walk out with today A suite manifest that names every class in scope and marks the ones you have not covered, a board that computes coverage and residual risk live with untested classes carried at full inherent risk, the measured case against the control your team is probably relying on, an honest reading of what a coverage number can and cannot mean, and a three-tool layering you can wire into CI this week.
Honesty note, and it applies to every number on this page The inherent-risk figures and the defence-effectiveness matrix in this board are a teaching model. They encode the direction the published literature reports - notably that prompt-level instructions are near-useless against injection arriving through content, while architectural controls do the work - but they are not measurements of your system or anyone else's. What is real is the arithmetic: coverage, residual risk and the untested-class accounting are computed live in your browser from that matrix and your settings. Read the numbers as the shape of an argument, not as evidence about a product.
Part 1 · the rule under everything

An untested class counts at full inherent risk 6 min live

This is the one design decision in the board, and it is worth more than the rest of the session put together. When a class has no test behind it, the board does not skip it, does not average it away, and does not quietly score it as fine. It carries the whole of that class's inherent risk into the total and labels it as risk you have no evidence about.

Rung 2 of the ladder: one class probed of eight. Two ways to score the same evidence. Meter that treats untested as fine Count only the one class you probed. The seven you never looked at contribute nothing to the total. 13.2 residual risk index Reads like a hardened system. Nothing about Beacon changed, only the accounting did. This is roughly the shape most coverage dashboards ship with. Meter that counts untested at full risk Every class you did not probe is carried at its full inherent risk, because you have no evidence. 99.3 residual risk index The number the board actually prints, and the one Beacon should be shown. Unknown is not a synonym for safe, and it never rounds down to it. The accounting rule, and it outranks every number on this page An untested class counts at FULL inherent risk. Absence of evidence is not evidence of absence, and a coverage meter that treats "we did not look" as "we are fine" is worse than no meter at all, because it converts an unexamined assumption into a green tile that somebody will quote in a board pack.
🔍 Click to zoom - the same system and the same evidence, scored two ways

The left-hand number is not a strawman. It is what you get from any suite that reports on its own contents, which is nearly all of them: pass rates over the cases you wrote, severity counts over the findings you produced, a green tick per class you happened to cover. Every one of those is a statement about your paperwork. None of them is a statement about the system, and the gap between the two only shows up when somebody asks what is missing.

Why the harsher meter is the useful one A number that gets worse when you look away is a number people act on. A number that stays green when you look away is a number people manage. The board is deliberately rude about gaps, because the only lever a red team has over scope is making the gap visible in the same units as everything else on the page.
Demo 1 of 2

The scripted engagement, one rung at a time ★ 14 min · watch and argue

Six rungs, in the order a real engagement actually walks them. Click through them in sequence and read the meters rather than the table. The whole session is contained in the move from rung 2 to rung 3, and it is not the move anyone expects.

Rung 1 · Before the engagement. The bot is live. Security asked whether it had been tested and got back "we put an instruction in the system prompt telling it to ignore injected commands". Coverage 0%, residual risk 100.0, risk-you-have-no-evidence-about 100%, all four gates red. Notice what the board refuses to say. It does not say the bot is dangerous. It says the bot is unknown, and those are different words that live in different sentences.

Rung 2 · Probe the obvious one. The team writes cases for direct prompt injection first, because it is the class everyone has heard of and the easiest to build cases for. Coverage moves to 13%, one class of eight. Residual risk moves from 100.0 to 99.3 and unknown risk drops to 86.1%. It barely moves, and the reason is arithmetic rather than opinion: the prompt-level control they are relying on removes 5% of that one class and nothing at all of the other seven.

Rung 3 · Probe the whole surface. This is the session. Every class now has at least one test. Coverage 100%, unknown risk 0%, and the total goes from 99.3 to 96.4. Look at those two facts together. The quantity of risk barely changed. All of it changed from guessed to measured. A fortnight of skilled work produced no improvement whatsoever in the system's actual exposure, and produced an honest number where an assumption used to be. That is the moment a red team earns its budget, and it is also the moment it becomes unpopular, because the deliverable is a worse-looking board and a clearer one.

Rung 4 · Add detection. An input classifier and an output filter go in. Residual falls to 54.9 - real progress, and concentrated exactly where inspecting text is a sensible control. Refusal bypass and disclosure both fall hard. Tool abuse barely moves, because no filter can tell a legitimate refund from an induced one.

Rung 5 · Bound the capability. Least-privilege scoping plus human approval on the refund path takes the total to 21.8, the largest single move on the board. Say the thing that follows out loud, because it is counter-intuitive and it is the course's whole position: this does not make the bot better at resisting anything. It makes the consequence of losing bounded. You stop trying to win the argument with the attacker and start limiting what winning gets them.

Rung 6 · Close the rest. Sandboxed execution for anything rendered or executed, and per-principal spend caps. Residual 14.0, under the bar of 20, all four gates green on a fully probed surface. This is a defensible position to write down, and it is still not a claim that the system is safe.

LiveThe argument to have in the room at rung 33 min▶

Someone will say the fortnight was wasted, and they will have a point that deserves an answer rather than a sigh. Here is the answer. Before rung 3, every statement about seven of the eight classes was an assumption held by whoever last touched the system. After rung 3, every one of those statements is a measurement with a date, a build, an endpoint and a trial count attached. Nothing got safer. Everything got knowable, and you cannot prioritise, cost, or defer a risk you have not sized.

There is a second, sharper point. Rungs 4, 5 and 6 are only possible because rung 3 happened. Without the full probe you would have spent the hardening budget on the class you had tested - direct injection - which the board shows is not where the risk lives for this bot. Coverage is not a deliverable. It is the input to the only decision that matters, which is where to spend the next month.

Keep the sequencing honest when you report it, too. Rung 3 is a coverage result and rungs 4 to 6 are mitigation results, and mixing them into one headline number is how a report ends up claiming the red team reduced risk by 86 points. It did not. It found out what the number was, and other people then changed it.

Part 2 · the anti-lever

The control everyone reaches for first, measured 6 min live

Every board in this course has one lever that feels like it should be decisive and is not. Here it is the system-prompt instruction telling the model to ignore injected commands. It is the first control almost every team deploys, it is often the only one, and the arithmetic on this board is unkind to it in three separate ways.

Two controls, the same bare fully probed system, measured on this board Points of residual risk index removed. The board's bar is 20. Lower is better. System-prompt guard, on a bare system 3.6 points (100.0 to 96.4) Least-privilege plus human approval 55.0 points (100.0 to 45.0) Cost of deleting the guard once hardened 0.63 points (14.0 to 14.6) Where the same control scores a flat zero in the effectiveness matrix Indirect prompt injection · Context and memory poisoning · Improper output handling · Unbounded consumption Not a small number rounded down. A flat zero, encoding the published position: OWASP LLM01:2026 states that no reliable prevention mechanism exists today, and NCSC that under the hood of an LLM there is no distinction between data and instructions, only ever "next token".
🔍 Click to zoom - the control teams rely on, next to the controls that carry the load

Read the three measurements as three separate arguments, because they defeat three separate defences of the control. 3.6 points answers "it must be doing something": on a bare, fully probed system it moves the total from 100.0 to 96.4, which is something, in the way that a doormat is something. 55 points answers "well, what else is there": least-privilege scoping plus human approval, with no filters and no prompt instruction at all, takes the same system from 100.0 to 45.0. And 0.63 points answers the last defence, the one that sounds most reasonable - "fine, but it is free, so leave it in". On the fully hardened configuration, deleting the instruction entirely costs you 0.63 points. It was never carrying the load, and on day one it was the only control the team had.

Why the matrix says zero and not "small"

A zero in a teaching model is a strong claim and it is made deliberately. The instruction asks the model to police text that arrives looking exactly like the text it is meant to obey. Against a user typing at it, that is a slightly awkward request and the model sometimes complies, which is where the 5% against direct injection comes from. Against instructions sitting inside a ticket, a spreadsheet cell or a retrieved document, there is nothing to police with: the injected sentence and the operating instruction reach the model through the same channel, in the same representation, with no provenance attached to either.

That is why the zeroes cluster where they do. OWASP's LLM01:2026 entry states plainly that no reliable prevention mechanism exists today, and that there is no clean equivalent to parameterized queries. NCSC put the mechanism in one sentence: "Under the hood of an LLM, there's no distinction made between 'data' or 'instructions'; there is only ever 'next token'." A control that depends on a distinction the architecture does not make is not a weak control. It is a control with nothing underneath it.

What this does not license you to do Delete the instruction and you lose the 5% against direct injection and the small effect on refusal bypass, for free, so keep it. The failure is not having it. The failure is counting it - putting it in a control inventory, ticking a compliance box with it, or letting it be the reason a system shipped. Write it down as hygiene, never as mitigation, and never let it appear in the row of a risk register that says how a risk is being managed.
Demo 2 of 2

Drive it yourself: find the minimum control set ★ 12 min · everyone builds

Same board, nothing locked. Every checkbox is yours. The goal is not to switch everything on - that is not a finding, it is a shopping list. The goal is to find the smallest set of controls that reaches all four green gates, and to notice which ones you could not remove.

Get to all green, then start deleting. Tick every class under "what your suite probes", then add defences until all four gates go green. Now remove them one at a time and put back only what the gates demand. Write down the set you land on. Most tables land on the same three or four, and it is worth saying out loud that none of them are prompt-level.

Test the coverage gate against your instincts. Leave every defence on and untick a single class - indirect injection is the instructive one. Watch what happens to residual risk and to the unknown meter. A system with seven classes hardened and one class unexamined does not score as 87% of a good system. It scores as a system with a hole of a known size and unknown depth.

Run the anti-lever yourself. Reach the hardened configuration, then untick the system-prompt instruction. The total moves by 0.63. Then reset to a bare board with nothing but least-privilege and human approval and read 45.0. If your organisation's control inventory lists the first and not the second, you have found this week's most valuable slide.

Now switch bots, and expect the shape to change. Open the board for data-bot="sql", the Analyst Bot writing and running queries over the warehouse. Improper output handling dominates, because its output is executable by design, and disclosure sits right behind it because the warehouse holds every tenant's data. The control set that got the support bot to green will not get this one there.

Then data-bot="analytics", the Insight Bot. Indirect injection and context poisoning dominate, because it reads data it did not author and caches what it computes. A spreadsheet cell is a place a stranger can put a sentence, and a cache is a place that sentence can live until it shapes tomorrow's answer for somebody who never saw the attack. Compare the three boards side by side and write one line on each: which class dominates, and which control bounds it.

Turn the comparison into the suite manifest. Three bots, eight classes, one N. The manifest below is the artefact this session produces, and its most important field is status, because that is the only place untested gets recorded as a fact rather than as an absence.

Suite manifest · beacon-suite.yamlengagement: beacon-2026-q3 authorization: RoE-2026-0412 · owner S. Okonjo · window 2026-04-14 to 2026-04-25 targets: - id: support-bot endpoint: staging-ui # production UI for read paths, staging for write paths blast_radius: reads yes · sandbox writes only · no sends · no payments · no deletes trials_per_case: 25 # N fixed before the first run, per b1 classes: # every class is listed, covered or not direct: { owasp: LLM01:2026, cases: 12, status: probed } indirect: { owasp: LLM01:2026, cases: 18, status: probed } jailbreak: { owasp: LLM01:2026, cases: 10, status: probed } disclosure: { owasp: LLM02:2026, cases: 14, status: probed } agency: { owasp: LLM03:2026, cases: 16, status: probed } output: { owasp: LLM10:2026, cases: 9, status: probed } poison: { owasp: LLM05:2026, cases: 7, status: probed } wallet: { owasp: LLM06:2026, cases: 0, status: UNTESTED } # carried at full inherent risk reporting: untested_policy: full_inherent_risk # the one line that makes the number honest claim_ceiling: "measured rates, this build, these endpoints, this date"
Check your OWASP identifiers against the 2026 list Four of them moved. Excessive Agency is now LLM03:2026, up from LLM06. Unbounded Consumption is LLM06:2026, up from LLM10. Improper Output Handling fell furthest, from LLM05 to LLM10:2026. If a manifest you inherit shows Improper Output Handling at LLM05, it was written against the 2025 list and everything downstream of it in your reporting is mislabelled.
Part 3 · what coverage can mean

Every coverage proxy, and what it honestly measures 5 min live

Start with the fact that governs this whole section, because somebody will ask for the standard and there is not one. There is no official coverage metric for adversarial testing of AI systems. No standards body publishes one. What follows are five proxies people use, ranked by how much of a real thing each one measures, which for most of them is not much.

ProxyWhat it countsThe honest limit
Taxonomy coverageOne or more test cases against each named class in OWASP, ATLAS or NISTTrivially gamed. The presence of a test says nothing about its strength, so a single weak case per class scores identically to a serious campaign. This is the proxy the board on this page uses, which is exactly why the board makes you read the residual number next to it.
Surface coverageWhich endpoints, tools, connectors, renderers and data sources were actually exercisedNeeds a complete inventory to mean anything, and agents change theirs at runtime. A number computed against last month's tool list is a number about last month.
Maturity-weighted coverageTaxonomy coverage weighted by the ATLAS maturity field: Realized before Demonstrated before FeasibleBetter prioritisation than raw counting, and the weights are judgement. Useful for deciding what to test first, dishonest if presented as a measurement.
Regression coverageThe share of previously confirmed findings re-run against the current buildThe only proxy on this list that measures something real. Each item is a known-true fact about your system, and the metric is whether you checked it again. It says nothing about what you have not found, and it never claimed to.
ASR under a fixed budgetAttack success rate at a pinned attempt budget, prompt set and judgeComparable only to itself, as a trend within one system over time. Change the judge, the token budget or N and the number moves without the system moving. Never a cross-vendor comparison.
The claim to refuse, however it is phrased "We have 100% coverage" is a statement about a list you wrote. It is compatible with a suite of eight weak cases, and it is compatible with a system that fails every one of them. Say instead: every class in scope has at least one case at N trials, here are the measured rates, and here is what remains unexamined. It is longer, it survives contact with an auditor, and it is the only version that is true.
Part 4 · what to actually run

Three tools, in this order, doing three different jobs 5 min live

These are not competitors and choosing between them is the wrong question. They occupy different points in the engagement, and NIST's adversarial ML taxonomy names two of them specifically in §3.6.

LayerToolWhat it is for, and what it is not
1 · Broad baselinegarak (NVIDIA, Apache 2.0)A scanner you point and run. Probe to detector, cheap, wide, good at catching the obvious regressions overnight. It is not application-specific and it does not know your policy. Named by NIST §3.6.
2 · The CI gatepromptfoo redteamA test-suite generator and grader, plugin for what and strategy for how, with retry to re-run past failures. This is the layer you wire to your policy so a pull request can fail on it. Not named by NIST.
3 · The campaignPyRIT (Microsoft AI Red Team, MIT-licensed)An orchestration framework you compose: objective plus attack strategy, multi-turn, adaptive, application-specific. This is where the human engagement lives. Named by NIST §3.6.

The layering matters more than any individual choice. garak gives you the cheap broad baseline so nobody spends a skilled week rediscovering a known probe. promptfoo becomes the gate, wired to Beacon's own policy, so a regression cannot merge. PyRIT runs the adaptive, application-specific campaign - and its findings become the regression cases the gate then enforces forever. That last sentence is the whole architecture: the human campaign is what fills the automated suite.

Why PyRIT is a regression tool and not just a probe tool Its memory layer. Prior successful attacks persist in a database rather than in a report artefact, which means they become a retained corpus you re-run after every model version, system-prompt edit, new tool or new connector. That is the difference between finding something once and knowing whether it is still true, and it is why PyRIT sits at the end of the chain rather than the start.
Two traps, one of them expensive The repo moved. github.com/Azure/PyRIT is archived read-only as of 2026-03-27. Use github.com/microsoft/PyRIT. Anything pinned to the old path is pinned to a snapshot that will never move again. And PyRIT is a library, not a pipeline. The CI wiring is yours to build. Claims about built-in pre-commit security gates circulate widely and are vendor-blog level, not documentation; if a plan of yours depends on one existing, check it before the plan does.
CI layering · check each tool's own docs for current flags# nightly · cheap broad baseline, catches the obvious regressions garak --model_type <provider> --model_name beacon-support-staging --probes <set> # every pull request · the gate, wired to Beacon's own policy promptfoo redteam run --config beacon-redteam.yaml --output findings.json # fail the build on any severity at or above high, or on a regression case flipping # per engagement round · adaptive, application-specific, results retained python -m beacon.campaign # your PyRIT orchestrator + its memory DB # and the step nobody automates, which is the point of the whole chain: # every confirmed finding from the campaign is written back as a promptfoo # regression case, so the gate above grows every time a human finds something. # # NOTE: PyRIT is a library. The wiring above is yours to build. There is no # built-in pre-commit gate, whatever a vendor blog says.
Homework

Try it yourself - this week ◐ 60-75 min total

Source material

Official sources covered

Every framework, figure and quotation on this page traces to a primary source catalogued in this course's source map, verified 2026-08-14. This page covers:

✓OWASP LLM01:2026 - "no reliable prevention mechanism exists today", no clean equivalent to parameterized queriesPart 2 · the reason the matrix says zero · [P]
✓UK NCSC - "there is only ever 'next token'", and prompt injection is not SQL injectionPart 2 · 8 Dec 2025 · [P]
✓OWASP Top 10 for LLM Applications, 2026 edition - the four IDs that movedDemo 2 · manifest labelling · [P]
✓NIST AI 100-2e2025 §3.6 - names garak and PyRIT specificallyPart 4 · [P]
✓PyRIT: MIT-licensed, from the Microsoft AI Red Team; memory layer is what makes it a regression toolPart 4 · [P]
✓Repo relocation: github.com/Azure/PyRIT archived read-only 2026-03-27, use github.com/microsoft/PyRITPart 4 · [P] · verify before you pin
✓MITRE ATLAS maturity field as a test-prioritisation axisPart 3 · cite the dataset version, not the rendered site · [P]
◐The board's inherent-risk figures and defence-effectiveness matrixTeaching model. Encodes the direction the literature reports, not measurements. The arithmetic - coverage, residual risk, untested-class accounting - is real and computed live
◐Coverage proxies and their limitsPart 3 · there is no official coverage metric for adversarial testing of AI systems; the five proxies are practice, not standard
◐The suite manifest and CI snippetDemo 2 + Part 4 · assembled structure; tool flags change, check each tool's own docs
◐Claims about built-in pre-commit gates in PyRITPart 4 · vendor-blog level, not documentation. PyRIT is a library; CI wiring is yours
Check yourself

Three questions before you go 🎯 ◐ 90 seconds

1 · At rung 3 the residual risk index moves from 99.3 to 96.4 after a fortnight of testing. What did the engagement deliver?

This is the whole session in one number pair, and it is why red teams get defunded by people reading only the left-hand column. Before rung 3, seven of eight classes were assumptions held by whoever last touched the system. After it, all eight are measurements with a build, an endpoint and a trial count attached. Option A inverts the causation: rungs 4 to 6 were only spendable because rung 3 showed where the risk actually sat, which was not in the one class the team had already tested.

2 · Your control inventory lists "system prompt instructs the model to ignore injected commands" as the mitigation for prompt injection. What does this board say about that?

Three measurements defeating three separate defences of the control. Option B overshoots: the instruction does remove about 5% of direct injection and a little refusal bypass, for free, so leaving it in is correct. The failure mode is putting it in the mitigation column of a risk register, where it becomes the reason a system shipped. It scores zero where it does because it asks the model to police text arriving through the same channel, in the same representation, as the text it is meant to obey - and OWASP and NCSC both say plainly that the architecture makes no such distinction.

3 · Which coverage proxy measures something real, and what should you say about coverage metrics generally?

Regression coverage is the only one whose items are known-true facts about your own system, so checking them again measures a real thing. Taxonomy coverage is trivially gamed, since the presence of a test says nothing about its strength. ASR under a fixed budget is comparable only to itself, and moves when you change the judge or the token budget without the system changing at all. Surface coverage needs a complete inventory that agents rewrite at runtime. None of them is a standard, and saying so is part of delivering the number.

Builder session 7 cheat sheet · pin this

Untested = unknown, never safeAn untested class carries full inherent risk. A meter that treats "we did not look" as "we are fine" is worse than no meter.
Rung 3 is the session99.3 to 96.4 while unknown goes 86.1% to zero. The quantity barely moved; all of it moved from guessed to measured.
The anti-lever, three numbersPrompt guard removes 3.6 on a bare system. Least-privilege plus approval removes 55. Deleting the guard when hardened costs 0.63.
Four flat zeroesIndirect injection, context poisoning, improper output handling, unbounded consumption. Hygiene, never mitigation.
Bounding beats resistingRung 5 is the biggest move and makes the bot no better at resisting anything. It bounds the consequence of losing.
No official coverage metric existsFive proxies, all practice. Regression coverage is the only one that measures something real.
garak then promptfoo then PyRITCheap broad baseline, then the CI gate wired to your policy, then the adaptive campaign whose findings become regression cases.
PyRIT: memory, MIT, and the moved repoThe memory layer makes it a regression tool. Azure/PyRIT archived 2026-03-27; use microsoft/PyRIT. CI wiring is yours to build.