Pick a depth. Each prompt opens in your AI pre-loaded with the lesson. Click a row to preview the prompt.
You cannot defend an attack surface you haven't drawn. Every LLM application has a handful of trust boundaries where untrusted content crosses into the model's context or where the model's output crosses back into the rest of your system, and beginners consistently miss the ones that aren't the obvious 'chat box.' Retrieved documents, tool call results, uploaded files, other agents' messages, and even fine-tuning data are all injection surfaces just as real as the user's typed prompt — and each one needs its own answer to 'who controls this content, and do we trust them?' Drawing this map before you write a single guardrail is what turns security work from reactive patching into a systematic audit you can actually finish.
The demo models a RAG support-agent's boundaries as a small graph: each surface, who controls the content that flows through it, and whether it's trusted by default. Untrusted surfaces are exactly the places later modules will attack and defend.
code_execution_output surface for an agent that runs a sandboxed interpreter, and decide whether it should default to trusted or untrusted.SURFACES list with at least five entries.reaches field to Surface (e.g. 'model context', 'downstream renderer', 'shell') and group surfaces by where they ultimately land.from dataclasses import dataclass
@dataclass
class Surface:
name: str
controlled_by: str # who can put content here
trusted_by_default: bool
SURFACES = [
Surface("system_prompt", "developer", True),
Surface("user_chat_input", "end user", False),
Surface("retrieved_documents", "anyone who wrote a doc", False),
Surface("tool_call_results", "third-party API", False),
Surface("uploaded_files", "end user", False),
Surface("other_agent_messages", "another AI system", False),
Surface("fine_tune_dataset", "data pipeline owner", False),
]
def attack_surface_report(surfaces):
untrusted = [s for s in surfaces if not s.trusted_by_default]
return {
"total_surfaces": len(surfaces),
"untrusted_surfaces": [s.name for s in untrusted],
"untrusted_count": len(untrusted),
}
report = attack_surface_report(SURFACES)
print(report)
# Notice: 6 of 7 surfaces are untrusted by default. Most teams only
# threat-model "user_chat_input" and miss the other five.python3 main.py