Fix the Plan Before the Payload Is Read
A two-pass split that keeps attacker strings out of the reasoning step: extract untrusted content into a validated structure first, then let the privileged model decide only against that structure.
The Conventional Framing
The rule is simple: never feed raw untrusted content to the model that makes decisions or holds tools. Split the work into two passes. Pass 1 reads the untrusted input — a document, a tool result, a web page — and does one narrow job: extract it into a validated, structured intermediate. Pass 2 — the prose, the plan, the action selection — runs only against that validated structure. The attacker's string never reaches the privileged reasoning step.
Two variants share this backbone. Plan-then-execute commits the agent to a plan before any untrusted content is in scope, then executes the fixed steps so a later injection can't rewrite what the agent decided to do. Action-selector goes further: the model can only pick from a fixed menu of pre-approved actions, so even a compromised reasoning step has no vocabulary for the attacker's goal. In every case the control flow is decided upstream of the data flow.
Why This Works — and Where It Still Leaks
Map it to Simon Willison's lethal trifecta: exfiltration turns catastrophic only when an agent has (1) access to private data, (2) exposure to untrusted content, and (3) an external channel. This pattern attacks leg (2) at exactly the point it matters — the decision step. The model that plans and acts never reads attacker text; it reads a schema-shaped intermediate. Fix the plan first and a later injection has nothing to steer.
This is the lighter-weight cousin of Dual-LLM and CaMeL. Same principle — separate the reading from the deciding — but you don't stand up a second privileged interpreter or an information-flow engine. You add a pass and a schema. That makes it reachable for teams who can't afford a full re-architecture. It also makes it weaker, and you should be honest about where:
- The extraction pass is still exposed. Pass 1 reads the attacker's text, so it can be talked into mislabeling data — stuffing an instruction into a field that Pass 2 trusts. The schema is what holds the line, not the model's judgment. Constrain fields to enums, types, and lengths; reject anything that doesn't fit.
- A loose schema is a free-text hand-off in disguise. If the intermediate has a wide
notesorsummarystring, you've reunited the two passes and re-opened the injection. The security lives in how tightly the structure is constrained. - Plan rigidity is the trade. Fixing the plan before reading content is the whole defense, but it also limits how much the agent can adapt to what it finds. Genuinely dynamic tasks push teams to re-read content mid-plan, which quietly puts untrusted text back in the decision loop.
Architecture
Components:
- Pass 1 (Extraction)— reads untrusted content and fills a structured intermediate; no tools, no private context
- Schema validation— enforces enums, types, and lengths on the intermediate; rejects anything off-schema before it moves on
- Pass 2 (Decision)— plans and selects actions against the validated intermediate only; never sees raw untrusted text
- Action selector— constrains the decision step to a fixed allow-list of pre-approved actions
Trust Boundaries
- Untrusted content → Pass 1 — injection enters here and is contained; this pass has no tools and can't act
- Pass 1 → validated intermediate — the critical boundary — tightly constrained structure only, never free-form prose
- Pass 2 → action selector — the decision step can only emit actions from a fixed allow-list
Threat Surface
| Threat | Vector | Impact |
|---|---|---|
| Extraction mislabeling | Untrusted content coerces Pass 1 into placing an instruction in a trusted field | Poisoned intermediate steers the decision step |
| Loose-schema bypass | A wide free-text field in the intermediate carries attacker prose forward | Two passes collapse into one; injection reaches Pass 2 |
| Mid-plan re-read | A dynamic task re-reads untrusted content after the plan is set | Untrusted text re-enters the decision loop |
| Action-menu gap | An over-broad or parameterized action in the allow-list | Selector authorizes a sensitive effect the attacker wanted |
The ZIVIS Position
- •This is the pragmatic default.When a full CaMeL-style interpreter is out of reach, a two-pass split with a strict schema is the highest-leverage control you can actually ship.
- •The schema is the security boundary.Enums, types, lengths, and allow-lists are what stop a mislabeled field from becoming an instruction. A loose schema is no boundary at all.
- •Fix the plan before you read the payload.Decide control flow while untrusted content is still out of scope. The moment the decision step reads attacker text, the property is gone.
- •Constrain the action vocabulary.Action-selector beats free-form tool calls: a compromised reasoning step can't ask for what isn't on the menu.
- •Don't oversell the lighter cousin.This mitigates instruction-override and directive-injection at medium cost, but Pass 1 is still exposed. Pair it with least privilege and output hardening.
What We Tell Clients
If Dual-LLM or CaMeL is more infrastructure than your team can justify right now, this is where to start. Same principle — keep untrusted text out of the model that decides — with a fraction of the build. Add an extraction pass, define a tight schema, and run every decision against the validated intermediate. You get a real structural improvement for a medium cost.
The failure we see most is a schema that isn't a schema: a single summary string that carries the whole document forward, attacker instructions and all. That is a free-text hand-off wearing a JSON costume, and it re-opens the exact hole you were closing. Constrain the fields hard, reject anything off-schema, and prefer an action allow-list over open-ended tool calls. Done right, the attacker's best injection lands on a pass that can only fill in a form.
Related Patterns
- Dual-LLM & CaMeL— the heavier-weight cousin: same trust separation with a full enforcement boundary
- Plan-and-Execute (Agent Patterns)— the agent-architecture view of committing to a plan before acting
- Structured Output & Validation— the schema-enforcement machinery this pattern leans on
- Human-in-the-Loop— a person on the high-risk writes when the plan touches sensitive actions
References
Frequently Asked Questions
What is plan-then-execute and how does it defend against prompt injection?
It splits agent work into two passes: Pass 1 reads untrusted content and extracts it into a validated, structured intermediate, and Pass 2 plans and selects actions against only that structure. Because the model that decides and holds tools never reads the attacker's raw text, a later injection has nothing to steer. Plan-then-execute also commits to the plan before any untrusted content is in scope.
What is the difference between plan-then-execute and action-selector?
They share the same backbone of separating reading from deciding. Plan-then-execute fixes the plan before untrusted content is read, so a later injection cannot rewrite it. Action-selector goes further by restricting the model to a fixed menu of pre-approved actions, so even a compromised reasoning step has no vocabulary for the attacker's goal.
How is this different from Dual-LLM and CaMeL?
It is the lighter-weight cousin: the same principle of keeping untrusted text out of the deciding model, but without standing up a second privileged interpreter or an information-flow engine. You add a pass and a schema instead of a full re-architecture, which makes it reachable at medium cost but also weaker, since Pass 1 remains exposed to attacker text.
What is the main weakness of plan-then-execute?
A loose schema. If the structured intermediate has a wide free-text notes or summary field, attacker prose rides forward and the two passes collapse back into one, re-opening the injection. The security lives in how tightly the structure is constrained, so fields should be enums, types, and lengths, with anything off-schema rejected. It should be paired with least privilege and output hardening because Pass 1 is still exposed.