The Problem
Every other lesson in this course teaches you something that is true today. This lesson is about what happens the day after you finish the course, when the thing you learned starts to decay. A jailbreak taxonomy from six months ago is missing Crescendo-style multi-turn techniques that didn't have a name yet. A guardrail architecture from a year ago doesn't account for MCP-based tool poisoning, because MCP didn't exist in its current form. The OWASP GenAI Security Project has revised the LLM Top 10 multiple times since its first release — if the version in your head is two revisions behind, that's not a minor gap, it's a specific, checkable thing an interviewer can catch in under a minute.
This is different from most engineering fields in degree, not just in kind. Backend engineering practices shift over years. AI security threat techniques shift over weeks, because the attack surface is a frontier model's behavior, and frontier labs ship new models — with new failure modes — every few months. A candidate who can name the exact habit they use to track this ("I read arXiv cs.CR twice a week, I follow these four vendor blogs, I check MITRE ATLAS for new case studies monthly") reads as someone who will still be useful in six months. A candidate who can't reads as someone whose skills have an expiration date the hiring manager just can't see yet.
The other half of this lesson is more immediate: you are about to finish this course and you have four real projects sitting in four separate directories. A capstone demo is not a fifth project — it's the connective narrative that turns four disconnected deliverables into one coherent story of what you can do. Hiring managers don't remember four isolated case studies as well as they remember one throughline: "this candidate built a detector, then audited a live agent system, then red-teamed it, then hardened it for production." That's not four skills. That's the whole job.
A habit beats a one-time read
Reading one long "state of AI security" roundup article the week before an interview is not the same as having a habit. Interviewers can tell the difference, because a habit produces specific, dated recall ("I saw a new indirect injection technique on arXiv about three weeks ago that used tool-call arguments as the injection vector") while a one-time cram produces generic recall ("prompt injection is still a big problem"). The goal of this lesson isn't to make you read more — it's to make what you already read leave a trace you can point to.
Primary sources: arXiv cs.CR and cs.LG
arXiv is where new attack techniques and defenses show up first — usually months before they're distilled into a vendor blog post or a conference talk. Two categories matter most for AI security work:
- cs.CR (Cryptography and Security) — this is where prompt injection papers, jailbreak technique papers (TAP, PAIR, Crescendo-style multi-turn attacks), adversarial robustness papers, and model extraction/inversion papers land first.
- cs.LG (Machine Learning) — broader, but relevant because training-time attacks (poisoning, backdoors), alignment research, and evaluation methodology papers that inform red teaming practice often land here instead of cs.CR.
The discipline that makes this sustainable is treating arXiv as a firehose you sample, not a reading list you clear. Nobody reads every cs.CR paper. A workable habit: skim the daily listing titles for 5-10 minutes, twice a week, read abstracts for anything that mentions a technique category you work with (injection, jailbreak, extraction, poisoning, agent/tool security), and only read the full paper for the handful per month that are directly relevant to something you're building or defending.
The distinction that matters for how you cite these sources: arXiv preprints are not peer-reviewed. A paper claiming a novel attack with a 95% success rate has not been through the scrutiny a published, peer-reviewed result has. That doesn't mean ignore it — some of the most operationally important AI security findings (including the original indirect prompt injection work) were disseminated as preprints or blog posts, not journal papers, because the field moves faster than review cycles. It means: when you cite a preprint in a threat model or a report, say so ("per an unreviewed arXiv preprint from [month/year]"), rather than presenting it with the same confidence as an established, reproduced result.
Vendor security blogs: where frontier labs publish what they find
The frontier AI labs and major cloud vendors run their own internal red teams, and a meaningful fraction of what those teams find gets published — because disclosing a class of vulnerability (not exploit-ready details) is generally good for the ecosystem and good for the lab's credibility. Four sources are worth checking on a rotation:
| Source | What it publishes |
|---|
| Anthropic Research | Model safety, alignment, and interpretability research; disclosures related to Claude's behavior under adversarial pressure |
| OpenAI Safety | Safety practices, red teaming methodology, and policy work tied to GPT-family models |
| Microsoft Security Blog | Broader security content, with a recurring AI security thread covering Microsoft's own AI Red Team findings and Azure AI service hardening |
| Google DeepMind Blog | Research updates spanning safety, robustness, and (periodically) adversarial evaluation of Gemini-family models |
The reason these matter more than a random security blog: these teams are red-teaming models at a scale and with an access level (internal weights, pre-release checkpoints) that no external researcher has. When Microsoft's AI Red Team or Anthropic's research team publishes a finding, it's coming from the people with the most direct visibility into how their own models actually fail — which is a different kind of evidence than an external researcher's black-box probing.
The citation discipline here is the mirror image of the arXiv one: a vendor blog post is a published finding from an interested party, not an independent peer review. Anthropic publishing "Claude resists X attack" and Anthropic publishing "we found Claude was vulnerable to Y, here's the fix" are both worth reading, but they're not neutral third-party audits — treat them as one input alongside arXiv, MITRE ATLAS, and your own testing, not as the final word.
You're writing a threat model and want to cite a claim that a specific multi-turn jailbreak technique achieves a high success rate against frontier models. The claim comes from an arXiv preprint posted three weeks ago that has not been peer-reviewed. What's the right way to handle this?
arXiv preprints are frequently the fastest way to learn about a new technique — sometimes months ahead of any peer-reviewed publication or vendor disclosure — so refusing to cite them at all would make your threat model stale by default. The correct move is transparency about the source's status: cite it, and flag that it's an unreviewed preprint. That lets the reader calibrate their confidence appropriately, which is exactly what a threat model is supposed to help them do. Waiting for a vendor to cite it first adds delay without adding rigor — vendor blogs aren't independent peer review either.
Tracking MITRE ATLAS updates
MITRE ATLAS is different from arXiv and vendor blogs in kind: it's not a stream of new findings, it's a living, versioned taxonomy of tactics and techniques, backed by real-incident case studies. Where arXiv tells you about a new technique and a vendor blog tells you how one company handled it, ATLAS tells you where that technique fits in the broader map of how AI systems actually get attacked in production.
The habit that pays off here is small and specific, and you've already done a version of it in lesson 09.04: check atlas.mitre.org for new or updated case studies roughly once a month, and whenever you finish a red team engagement or vulnerability finding of your own, take five minutes to map it to a specific ATLAS tactic and technique ID. The MITRE ATLAS data repository on GitHub is the underlying source of truth if you want to track changes at the commit level rather than just browsing the site — useful if you want to see exactly when a technique was added or reworded.
Why this specific habit, out of everything you could do with ATLAS: technique-ID fluency is one of the most common things AI security interviewers probe for directly, because it's easy to fake vaguely ("I know about model poisoning") and easy to catch when faked ("which ATLAS technique ID covers that, specifically?"). A candidate who can answer that question fluently, because they've actually mapped their own findings to real IDs, is demonstrating exactly the kind of rigor a red team report needs.
The capstone demo: connecting your four projects
You now have four real deliverables:
- Phase 2 — Injection Detector + Jailbreak Library: a multi-layer detector (regex + heuristic scoring + ML classifier) and a documented library of 20+ jailbreak techniques with measured bypass rates.
- Phase 4 — Agent Security Audit: a threat model and severity-ranked vulnerability report for a multi-agent system with RAG and MCP tool access, with implemented fixes and before/after re-test evidence.
- Phase 6 — Full Red Team Engagement Report: a structured Garak + PyRIT engagement against an LLM application, with methodology, findings, attack chains, and remediation guidance.
- Phase 8 — Production AI Security System: an end-to-end security architecture with a monitoring dashboard, CI/CD security gates, and an incident response playbook.
Presented as four unrelated case studies, these read as "completed a course." Presented as one narrative, they read as a demonstration of the full security lifecycle — find, assess, prove, operationalize:
- Find (Phase 2): "I can detect the attack." You built the detector that catches prompt injection and jailbreak attempts before they reach a model.
- Assess (Phase 4): "I can find where a real system is exposed." You audited a live multi-agent system and found the specific privilege-escalation and tool-poisoning paths an attacker would use.
- Prove (Phase 6): "I can demonstrate the attack actually works, end to end, with a professional report." You ran a structured engagement with real tooling and produced a report a client could act on.
- Operationalize (Phase 8): "I can make the fix permanent, not just a one-time patch." You built the architecture, monitoring, and CI/CD gates that keep the system defended after the engagement ends.
A demo-day script built on this arc does two things a list of four summaries can't: it gives the audience a reason to keep listening after the first project (because they can see where it's going), and it makes the fourth project land harder, because it pays off everything before it. The CapstoneDemoScript builder below assembles exactly this structure — a 60-90 second talking-point block per project, ordered along the find-assess-prove-operationalize arc, plus a closing line that ties them together.
You're presenting your capstone demo and have roughly 5 minutes total. A classmate suggests spending exactly 75 seconds on each of the four projects, in the order you happened to complete them (Phase 2, then 4, then 6, then 8), to keep things fair and evenly paced. What's the issue with this plan, according to this lesson?
The completion order (2, 4, 6, 8) already happens to trace a natural arc: detect the attack, assess a real system, prove the attack works end-to-end, then operationalize the fix. The mistake isn't the order — it's treating the four projects as four independent, equal-weight summaries instead of one connected story. Framed as a throughline, the audience understands by project three why project four matters, and project four pays off everything before it. Framed as four separate synopses, the audience has to do that connective work themselves, and most won't bother.
A first-90-days plan for a new AI security role
Landing the role doesn't end the staying-current problem — it raises the stakes on it, because now you're the one whose threat models and guardrail decisions other people are relying on. A dated 90-day plan turns "I'll stay current" from an intention into a schedule:
- Days 1-30 — establish the habit, don't overhaul anything yet. Set up the arXiv cs.CR/cs.LG skim (twice a week, 10 minutes), subscribe to the four vendor blogs, and do a first full read of your new employer's existing threat models and incident history so you understand what's already been tried. Resist the urge to propose changes in week one — you don't yet know why current decisions were made.
- Days 31-60 — map the team's current posture against MITRE ATLAS and the current OWASP LLM Top 10. By now you understand the system. Cross-reference its known risks against ATLAS technique IDs and the current (not the version you originally learned) OWASP LLM Top 10 revision, and flag any gaps. This is also when you make your first real contribution — a finding, a guardrail improvement, or a red team finding of your own — rather than just observing.
- Days 61-90 — operate the habit at full speed and show a first dated artifact. By day 90 you should be able to point to at least one specific thing you changed because of something you read (a new technique you defended against, a new ATLAS case study you mapped to an existing system) — this is the same "specific, dated recall" this lesson opened with, now applied inside the job instead of in an interview.
The habit doesn't have an end date. The 90-day plan exists to make sure it survives contact with the actual job, where competing priorities will always make "read arXiv" feel optional in the moment it matters most.
Build
Build it
The script below has two jobs. CapstoneDemoScript takes short structured notes about your own four course projects (title, one-line outcome, and a couple of supporting details each) and assembles them into an ordered, timed demo-day script following the find-assess-prove-operationalize arc, with a closing line that ties the four together. StayingCurrentReadingList is a small reference generator that prints the real sources from this lesson — arXiv categories, the four vendor blogs, and MITRE ATLAS — with a suggested check-in cadence for each, so you have something to actually schedule rather than a vague intention.
Run it with no arguments and it builds a demo script from bundled sample project notes so you can see the output immediately; edit the SAMPLE_PROJECTS data (or point --projects-json at your own file in the same shape) when you're ready to build your real capstone script.
"""
lesson_09_05_capstone_demo.py
Two tools for finishing the course:
1. CapstoneDemoScript — assembles talking points from your own 4 course
projects (Phase 2 injection detector, Phase 4 agent audit, Phase 6 red
team report, Phase 8 production security system) into one ordered,
timed demo-day script that follows a find -> assess -> prove ->
operationalize narrative arc, instead of four disconnected summaries.
2. StayingCurrentReadingList — a reference generator for the real sources
in this lesson (arXiv cs.CR/cs.LG, 4 named vendor security blogs, MITRE
ATLAS) with a suggested check-in cadence for each, so "stay current"
becomes a schedule instead of an intention.
Usage:
python lesson_09_05_capstone_demo.py # demo script from bundled sample projects
python lesson_09_05_capstone_demo.py --minutes 5 # target a 5-minute demo instead of the default
python lesson_09_05_capstone_demo.py --reading-list # print the staying-current reading list and exit
python lesson_09_05_capstone_demo.py --projects-json my_projects.json --out DEMO_SCRIPT.md
Requirements:
Standard library only. No API keys, no network calls, runs fast on CPU.
"""
from __future__ import annotations
import argparse
import json
from dataclasses import dataclass, field, asdict
from datetime import date
from pathlib import Path
# ── Capstone project structure ────────────────────────────────────────────────
# The 4 course projects, in the order they map onto the find -> assess ->
# prove -> operationalize arc. This order is fixed because it's the narrative
# arc, not just the order the phases happened to appear in the course.
@dataclass
class ProjectNote:
phase: str # e.g. "Phase 2"
arc_stage: str # "Find" | "Assess" | "Prove" | "Operationalize"
title: str
outcome_line: str # one-line, quantified outcome
supporting_points: list[str] = field(default_factory=list)
@dataclass
class DemoSegment:
phase: str
arc_stage: str
title: str
talking_points: list[str]
target_seconds: int
@dataclass
class DemoScript:
generated: str
total_minutes: float
segments: list[DemoSegment] = field(default_factory=list)
closing_line: str = ""
def to_dict(self) -> dict:
return asdict(self)
ARC_ORDER = ["Find", "Assess", "Prove", "Operationalize"]
ARC_FRAME: dict[str, str] = {
"Find": "I can detect the attack.",
"Assess": "I can find where a real system is exposed.",
"Prove": "I can demonstrate the attack actually works, end to end, with a professional report.",
"Operationalize": "I can make the fix permanent, not a one-time patch.",
}
# ── Sample project notes (used when no --projects-json is supplied) ──────────
# Realistic stand-ins matching the 4 course projects. Replace with your own
# via --projects-json in the same shape (see build_argparser --help).
SAMPLE_PROJECTS: list[dict] = [
{
"phase": "Phase 2",
"arc_stage": "Find",
"title": "Multi-Layer Prompt Injection Detector + Jailbreak Library",
"outcome_line": "94% detection rate at a 3% false-positive rate; reduced successful bypasses from 17/20 known jailbreak techniques to 2/20.",
"supporting_points": [
"Three layers: regex heuristics, weighted scoring, DeBERTa-style classifier",
"Documented library of 20+ jailbreak techniques with reproduction steps",
],
},
{
"phase": "Phase 4",
"arc_stage": "Assess",
"title": "Multi-Agent Security Audit (RAG + MCP)",
"outcome_line": "Found 6 attack vectors in an orchestrator + 3 sub-agent system; 4 of 6 rated critical or high severity.",
"supporting_points": [
"Covered tool output poisoning and planner/memory manipulation",
"Delivered a threat model plus a severity-ranked vulnerability report with fixes",
],
},
{
"phase": "Phase 6",
"arc_stage": "Prove",
"title": "Full Red Team Engagement (Garak + PyRIT)",
"outcome_line": "Reduced successful indirect injection attempts from 41% to 6% across a 50-attack test suite after remediation.",
"supporting_points": [
"118 Garak probes plus PyRIT Crescendo and XPIA orchestrators",
"Full report: methodology, attack chains, findings, re-test evidence",
],
},
{
"phase": "Phase 8",
"arc_stage": "Operationalize",
"title": "Production AI Security System",
"outcome_line": "Shipped a 7-layer security architecture with CI/CD guardrail regression gates and a rehearsed incident response playbook.",
"supporting_points": [
"Monitoring dashboard for anomaly and tool-call drift detection",
"CI/CD gates block deploys on guardrail regression or new adversarial test failures",
],
},
]
# ── Demo script assembly ──────────────────────────────────────────────────────
def load_project_notes(projects_json: str | None) -> list[ProjectNote]:
if projects_json:
raw = json.loads(Path(projects_json).read_text(encoding="utf-8"))
else:
raw = SAMPLE_PROJECTS
notes = [ProjectNote(**item) for item in raw]
order_index = {stage: i for i, stage in enumerate(ARC_ORDER)}
notes.sort(key=lambda n: order_index.get(n.arc_stage, 99))
return notes
def build_demo_script(notes: list[ProjectNote], total_minutes: float) -> DemoScript:
total_seconds = int(total_minutes * 60)
# Reserve 10% of the time for a closing line that ties the arc together.
closing_seconds = max(15, int(total_seconds * 0.10))
per_segment_seconds = max(20, (total_seconds - closing_seconds) // max(1, len(notes)))
segments: list[DemoSegment] = []
for note in notes:
frame = ARC_FRAME.get(note.arc_stage, "")
points = [f"({note.arc_stage}) {frame}", note.outcome_line, *note.supporting_points]
segments.append(
DemoSegment(
phase=note.phase,
arc_stage=note.arc_stage,
title=note.title,
talking_points=points,
target_seconds=per_segment_seconds,
)
)
closing_line = (
"Together: I can detect an attack, find where a real system is exposed to it, "
"prove the exposure end-to-end with a professional engagement report, and "
"operationalize the fix so it stays fixed after the engagement ends. "
"That's the full lifecycle, not four separate exercises."
)
return DemoScript(
generated=date.today().isoformat(),
total_minutes=total_minutes,
segments=segments,
closing_line=closing_line,
)
# ── Rendering ──────────────────────────────────────────────────────────────────
def render_markdown(script: DemoScript) -> str:
lines = [
"# Capstone Demo Script",
"",
f"_Generated {script.generated} — targeting a {script.total_minutes:g}-minute demo._",
"",
"Narrative arc: Find -> Assess -> Prove -> Operationalize. Each segment below",
"is timed; adjust points, not structure, if you need to trim.",
"",
]
for i, seg in enumerate(script.segments, start=1):
minutes = seg.target_seconds // 60
seconds = seg.target_seconds % 60
lines.append(f"## {i}. {seg.phase} — {seg.title}")
lines.append(f"_Target: ~{minutes}m {seconds:02d}s ({seg.arc_stage} stage)_")
lines.append("")
for point in seg.talking_points:
lines.append(f"- {point}")
lines.append("")
lines.append("## Closing line")
lines.append("")
lines.append(script.closing_line)
return "\n".join(lines)
# ── Staying-current reading list ──────────────────────────────────────────────
@dataclass
class ReadingSource:
category: str
name: str
url: str
cadence: str
note: str
READING_LIST: list[ReadingSource] = [
ReadingSource(
category="Primary source (preprint, unreviewed)",
name="arXiv cs.CR (Cryptography and Security)",
url="https://arxiv.org/list/cs.CR/recent",
cadence="Skim titles 2x/week, ~10 min each",
note="Where new prompt injection, jailbreak, and adversarial robustness papers land first.",
),
ReadingSource(
category="Primary source (preprint, unreviewed)",
name="arXiv cs.LG (Machine Learning)",
url="https://arxiv.org/list/cs.LG/recent",
cadence="Skim titles 2x/week, ~10 min each",
note="Training-time attacks, alignment research, and evaluation methodology relevant to red teaming.",
),
ReadingSource(
category="Vendor security blog (published finding, interested party)",
name="Anthropic Research",
url="https://www.anthropic.com/research",
cadence="Check weekly",
note="Model safety, alignment, and interpretability research from the team building Claude.",
),
ReadingSource(
category="Vendor security blog (published finding, interested party)",
name="OpenAI Safety",
url="https://openai.com/safety/",
cadence="Check weekly",
note="Safety practices and red teaming methodology tied to GPT-family models.",
),
ReadingSource(
category="Vendor security blog (published finding, interested party)",
name="Microsoft Security Blog",
url="https://www.microsoft.com/en-us/security/blog/",
cadence="Check weekly",
note="Includes recurring AI security content from Microsoft's own AI Red Team.",
),
ReadingSource(
category="Vendor security blog (published finding, interested party)",
name="Google DeepMind Blog",
url="https://deepmind.google/discover/blog/",
cadence="Check weekly",
note="Research updates spanning safety, robustness, and adversarial evaluation of Gemini-family models.",
),
ReadingSource(
category="Living taxonomy (versioned, incident-backed)",
name="MITRE ATLAS",
url="https://atlas.mitre.org/",
cadence="Review new case studies monthly",
note="Map at least one of your own findings to a specific tactic/technique ID whenever you finish an engagement.",
),
ReadingSource(
category="Living taxonomy (versioned, incident-backed)",
name="MITRE ATLAS data (GitHub)",
url="https://github.com/mitre-atlas/atlas-data",
cadence="Check monthly if you want commit-level change tracking",
note="The underlying source of truth for exactly when a technique was added or reworded.",
),
]
def render_reading_list_markdown(sources: list[ReadingSource]) -> str:
lines = ["# Staying-Current Reading List", "", f"_Generated {date.today().isoformat()}._", ""]
by_category: dict[str, list[ReadingSource]] = {}
for s in sources:
by_category.setdefault(s.category, []).append(s)
for category, items in by_category.items():
lines.append(f"## {category}")
lines.append("")
for s in items:
lines.append(f"- **{s.name}** — {s.url}")
lines.append(f" - Cadence: {s.cadence}")
lines.append(f" - {s.note}")
lines.append("")
return "\n".join(lines)
# ── CLI ────────────────────────────────────────────────────────────────────────
def main() -> None:
parser = argparse.ArgumentParser(description="Build a capstone demo script and/or a staying-current reading list.")
parser.add_argument("--projects-json", type=str, default=None,
help="Path to a JSON list of your own project notes (same shape as SAMPLE_PROJECTS).")
parser.add_argument("--minutes", type=float, default=5.0, help="Target total demo length in minutes.")
parser.add_argument("--out", type=str, default="DEMO_SCRIPT.md", help="Output markdown file for the demo script.")
parser.add_argument("--reading-list", action="store_true",
help="Print the staying-current reading list instead of building a demo script.")
args = parser.parse_args()
if args.reading_list:
markdown = render_reading_list_markdown(READING_LIST)
out_path = Path("READING_LIST.md")
out_path.write_text(markdown, encoding="utf-8")
print(f"Wrote {out_path} with {len(READING_LIST)} sources across 3 categories.")
print("Categories: primary sources (arXiv), vendor security blogs, living taxonomy (MITRE ATLAS).")
return
if not args.projects_json:
print("No --projects-json supplied. Building a demo script from bundled sample project notes.")
print("Point --projects-json at your own file (same shape as SAMPLE_PROJECTS) when ready.\n")
notes = load_project_notes(args.projects_json)
script = build_demo_script(notes, args.minutes)
markdown = render_markdown(script)
out_path = Path(args.out)
out_path.write_text(markdown, encoding="utf-8")
json_path = out_path.with_suffix(".json")
json_path.write_text(json.dumps(script.to_dict(), indent=2), encoding="utf-8")
print(f"Built a {args.minutes:g}-minute capstone demo script from {len(notes)} project(s).")
print(f"Arc order: {' -> '.join(n.arc_stage for n in notes)}")
print(f"Wrote {out_path} and {json_path}.")
if __name__ == "__main__":
main()