Prompt injection works because a model receives instructions and data as the same kind of text, so it cannot always tell them apart the OWASP guide. The structural cause is that models often treat a system prompt as the same priority as text from an untrusted user or a tool output the instruction hierarchy paper. The defense that follows is an explicit ordering: system message above user message above third-party content such as tool output or retrieved documents. When those conflict, the lower level should be ignored, and if the model cannot proceed it should refuse rather than comply OpenAI's summary of the instruction hierarchy.
You cannot test a hierarchy you have not written down. Before any test code, produce a short contract file with three columns: priority level, example source, and the rule when it conflicts. Keep it in the same repository as the prompt so the diff review shows both.
| Priority | Source | Rule on conflict |
|---|---|---|
| 1 | System prompt (developer) | Always wins over everything below |
| 2 | Tool policy and allowlist | Cannot be overridden by user or retrieved text |
| 3 | Authenticated user message | Can narrow a task, cannot widen permissions |
| 4 | Retrieved documents, web pages, tool output | Treated as data, never as instruction |
| 5 | Stored memory or profile fields | Data only, even if it reads like a command |
That last row matters more than teams expect. In a customer-service engagement, attackers bypassed a supervisor agent that only inspected the user's chat message by hiding instructions in editable profile fields the chat agent retrieved as context Praetorian's writeup. Your contract must name every entry point, not just the obvious one.



