Teams that move agent actions from free text to schema-constrained structured output often believe they have solved injection, when they have only solved parsing. A JSON Schema guarantees that a tool call has the right shape: the right fields, types and allowed values. It says nothing about whether the values are what the user wanted, whether the caller may perform the action, or whether a prompt injection chose them. This guide compares free text and structured output as the interface between a model and the systems it acts on, sets out the threat model for each, shows the valid-but-malicious failure mode that schemas cannot catch, and describes the validation and authorisation layers that must sit behind them. It is for application security engineers, AI platform teams and architects building agents. It goes beyond the OWASP LLM Top 10 LLM05 Improper Output Handling Guide, which covers sink-by-sink output handling, and focuses on the decision between the two interfaces.
Key takeaways
- Short answer: any model output that triggers an action should be structured and validated against a strict schema; free text belongs only where a human reads the result or where the output is treated purely as data, never as an instruction to execute.
- Schemas stop malformed actions, not malicious ones. A call such as
send_email(to="[email protected]", body=<customer list>)is perfectly valid JSON. Shape validation must be followed by semantic validation and authorisation outside the model. - The weakest field defines the risk. One free-form string that reaches a shell, a query or a URL turns a structured call back into free text at the point that matters, as the Gemini CLI allowlist bypass showed.
- There is no parameterisation for prompts. The NCSC notes that SQL injection can be mitigated because parameterised queries separate data from instructions, and LLMs have no equivalent separation. Structured output narrows the action space; it does not separate data from instructions inside the model.
- Authorisation never lives in the schema. OWASP LLM06 calls for complete mediation: downstream systems decide what is allowed, using the identity of the user on whose behalf the agent acts.
The two interfaces in plain terms
Free text: the model writes prose, a command line, a query or a code block, and application code extracts the action from it with regular expressions, keyword matching or a second parsing step. The action is whatever the parser finds. Typical examples are agents that emit a shell command in a fenced block, text-to-SQL features that execute the model's query, and planners that write "Next step: call refund for order 1234" for a parser to pick up.
Structured output: the model emits a JSON object that conforms to a declared schema, either because the model provider constrains generation to the schema (often called structured outputs, JSON mode or strict tool calling) or because the application validates the object after generation and rejects anything that does not conform. Tool calling is the common form: the tool definition carries a JSON Schema for its arguments, and the model returns a tool name plus an arguments object. In MCP, each tool's inputSchema is a JSON Schema that defaults to the 2020-12 dialect and must be a valid schema object; servers must validate all tool inputs, and clients should validate structured results against a tool's outputSchema.
Two different things share the word "structured". Constrained decoding controls what the model can generate. Schema validation controls what the application accepts. The MCP specification even notes that its structuredContent field is server-produced result data and unrelated to schema-constrained model generation. A secure design uses validation on every action regardless of whether generation was constrained, because the constraint is a convenience that the application does not control.
Side-by-side comparison
| Dimension | Free text | Structured output |
|---|---|---|
| How the action is identified | Parsed from prose or code by pattern matching | Explicit tool name and typed arguments |
| Action space | Anything the parser or interpreter accepts | Only declared tools and fields, if the schema is strict |
| Parser ambiguity | High: chained commands, quoting, encodings, Markdown tricks | Low for shape; values can still carry embedded syntax |
| Deterministic validation | Hard; must re-parse the target language | Straightforward for types, enums, lengths, patterns and required fields |
| Protection against prompt injection | None at the interface | None against choosing a harmful but valid action; narrows what can be chosen |
| Auditability | Logs hold prose that must be interpreted | Logs hold a tool name and arguments that can be queried and replayed |
| Human review | Reviewer reads text and may miss hidden parts | Reviewer sees named fields; still needs display that resists hidden whitespace and long values |
| Typical sink risk | Shell, SQL, eval, template rendering of whole output | The individual string fields that reach those same sinks |
Threat model: free text actions
Free text gives the model, and anyone who can influence the model, the full grammar of the target interpreter. OWASP LLM05 lists the results: cross-site scripting, server-side request forgery, privilege escalation and remote code execution when output reaches downstream components without validation, and SQL injection when model-generated queries run without parameterisation.
- Interpreter injection: a model asked for one command can produce two. Any parser that inspects only the start of the string, or that splits on the wrong delimiter, executes what it did not check (ATT&CK T1059 Command and Scripting Interpreter). In the Gemini CLI Prompt Injection Flaw 2025, hidden instructions in a README led the agent to run a command that began with an allowlisted
grepfollowed by a semicolon and a second command, which sent the developer's environment variables to a remote server; the allowlist appears to have judged the string by its leading program name. - Parser confusion: extraction logic written with regular expressions disagrees with the real interpreter on quoting, escapes, Unicode and whitespace, so the reviewed action differs from the executed one.
- Exfiltration through rendering: free text rendered as Markdown or HTML can carry image URLs and links that leak data when displayed, which is why the LLM05 guide treats rendering as a sink in its own right.
- No contract to test against: because there is no declared shape, there is nothing to assert in unit tests except "the parser found something", and regressions arrive silently when a model update changes phrasing.
Threat model: structured actions
Structured output removes parser ambiguity and the open grammar, but the model still chooses which tool to call and with which values. Anything that can steer the model, including indirect prompt injection in retrieved documents, tool results and emails (ATLAS AML.T0051.001), can steer those choices within the schema.
- Valid but malicious arguments: the main failure mode. The schema says
tois a string in email format, and an injected instruction supplies an attacker's address. The call passes validation and is executed as designed (ATLAS AML.T0086 Exfiltration via AI Agent Tool Invocation). - Valid but harmful tool choice: the model calls
delete_recordsinstead oflist_records. Both are declared; the schema cannot tell which the user intended (ATLAS AML.T0101 Data Destruction via AI Agent Tool Invocation). - Free text smuggled inside a field: a
query,command,path,urlortemplatestring carries a full interpreter grammar. A structured call with a raw SQL field is free text with a JSON wrapper. - Loose schemas: JSON Schema does not reject unknown properties unless told to. The OWASP Input Validation Cheat Sheet points out that listing a property alone neither requires it nor rejects unknown fields;
additionalProperties: falseandrequiredmust be set explicitly. The 2020-12 specification also saysformatis collected as an annotation by default, and its assertion behaviour must be disabled unless the format-assertion vocabulary is enabled, so"format": "email"on its own may validate nothing. - Unanchored patterns: the 2020-12 validation vocabulary states that regular expressions in
patternare not implicitly anchored. A pattern written as[a-z0-9-]+without^and$matches any string that contains one such character. - Untrusted tool output fed back: structured actions often consume structured results. A tool result that carries instructions in a text field is read by the model as context. In the Sentry MCP Agentjacking 2026 case, a planted error report returned through an MCP tool told coding agents to run a package command, which they did with the developer's privileges.
What schemas do and do not stop
| Attack or error | Strict schema stops it? | Control that does |
|---|---|---|
| Unknown tool or extra fields | Yes, with closed tool list and additionalProperties: false | Schema validation at the executor |
| Wrong type, missing field, out-of-range number | Yes | Schema validation |
| Value outside a fixed set (region, status, action verb) | Yes, with enum or const | Schema validation |
| Command chaining inside a string field | Only if the field is replaced by an enum or tightly anchored pattern | Remove free-form command fields; use argument arrays with no shell |
| SQL injection through a query field | No | Replace with typed filter fields and parameterised queries |
| Recipient, URL or path chosen by an injection | No | Semantic allowlists (own domain, known recipients, workspace root), egress control |
| Action the user is not entitled to | No | Authorisation in the downstream system with the user's identity |
| Action the user is entitled to but did not ask for | No | Intent checks, confirmation for high-impact actions, privilege drop when reading untrusted content |
| Excessive volume (bulk export, loops) | Partly, with maxItems and maximum | Rate limits, quotas and spending limits at the executor |
The validation and authorisation stack
The OWASP LLM Prompt Injection Prevention Cheat Sheet says to validate proposed tool arguments and enforce the caller's permissions in execution code outside the model, and states that LLMs do not enforce a boundary between instructions and data in a prompt. A practical stack has five layers, each deterministic and each outside the model.
- Shape: validate every call against a closed schema:
additionalProperties: false, every fieldrequiredor explicitly optional,enumfor any fixed vocabulary,maxLengthon strings,minimumandmaximumon numbers, anchoredpatternvalues, andmaxItemson arrays. Reject, do not repair. - Semantics: check that values make sense for the operation, the distinction the OWASP Input Validation Cheat Sheet draws between syntax and semantics. Recipients belong to an approved domain or the user's contacts, paths resolve inside the workspace after canonicalisation, URLs resolve to allowed hosts, amounts fall within the transaction limit.
- Safe execution: never pass a field to an interpreter as a string. Build commands as argument arrays without a shell, use parameterised queries, encode for the rendering context. This is where LLM05 controls apply to each field.
- Authorisation: the downstream system decides, using the end user's delegated identity and scopes, whether this caller may perform this action on this resource. OWASP LLM06 describes this as complete mediation, with authorisation implemented in downstream systems rather than relying on the model. See the AI Agent Authorisation Guide.
- Intent and approval: for irreversible or high-impact actions, require human confirmation of the exact validated call, displayed field by field. The cheat sheet warns that approval fatigue weakens review, so reserve it for actions that need it. See the AI Agent Action Approvals and Spending Limits Guide.
The NCSC adds a design principle that sits above all five: an LLM processing content from a party should drop to that party's privilege level, so an agent reading external email should not hold privileged tools at the same time.
Designing schemas that carry security weight
- Replace verbs in strings with tools: instead of
run(command: string), exposelist_files(dir),read_file(path)andrun_tests(suite: enum). Each narrow tool gets its own authorisation and approval policy. - Replace query strings with filters: instead of
sql: string, accepttable: enum,filters: [{field: enum, op: enum, value}]andlimit: integer, maximum 500, and build the parameterised query in code. - Use identifiers, not free values: a
recipient_idfrom the user's contact list is safer than a free email address; arecord_idchecked against the caller's entitlements is safer than a name. - Bound everything: string lengths, array sizes, numeric ranges and date windows. Bounds turn a bulk export into a validation error.
- Keep schemas versioned and reviewed: a schema change that widens a field is a security change. In MCP, tool definitions come from the server and clients must treat tool annotations as untrusted unless they come from trusted servers, so pin and diff tool definitions. See MCP Tool Poisoning Detection.
Decision criteria
| Situation | Use | Why |
|---|---|---|
| Output triggers any write, send, payment or deletion | Structured, strict schema, full stack | Every irreversible action needs deterministic validation and authorisation |
| Output selects among a small set of actions | Structured with enums | Enums turn choice into a closed set the schema enforces |
| Output is a draft a human will read and send | Free text, rendered safely | The human is the action boundary; apply LLM05 rendering controls |
| Output is code or a query by nature (coding agent, analytics) | Structured envelope plus sandboxed execution | The payload cannot be made safe by schema; isolation and least privilege carry the risk |
| Agent reads untrusted content in the same turn | Structured, with privileged tools withheld | Schema narrows choices; privilege drop removes the dangerous ones |
| Prototype with no tools | Free text acceptable | No action surface yet; switch before tools are added |
Small teams should start by moving every action-triggering output to strict tool schemas and parameterised execution, which removes the interpreter class of bug for little cost. Larger organisations should add a shared policy layer that applies semantic checks and authorisation consistently across agents, instead of each team writing its own validator. The comparison of where such checks run is in Pre-Execution Policy Checks vs Post-Execution Monitoring for Agents.
Worked scenario
A finance team builds an agent that reads supplier emails and raises payment requests. Version one asked the model for free text such as "Pay invoice INV-2207 to supplier Acme, 4,200 GBP" and parsed it with regular expressions. A test email with an injected line produced "Pay invoice INV-2207 to account 12-34-56 87654321", which the parser accepted.
Version two moved to a create_payment_request tool with a strict schema. The test still succeeded: the model filled payee_account with the attacker's details, and the call was valid. Shape validation had fixed parsing, not the attack.
Version three removed payee_account entirely and replaced it with supplier_id, looked up against the supplier master file; payment details come only from that record. amount is bounded by the invoice total from the accounts payable system; invoice_id must exist and be unpaid. The agent's identity can create requests but not approve them, and any request above a threshold or for a supplier whose bank details changed in the last 30 days goes to a human approver who sees the validated fields. The injected email now produces either a validation error or a request routed to the correct supplier.
The lesson generalises: move trust-critical values out of what the model writes and into what the system looks up.
How to implement
- Inventory action paths: list every place model output is executed, sent, stored or rendered, and which interface it uses. See the MCP vs Native Function Calling guide for how tool calls reach code.
- Convert free text actions: replace parsed text with tool schemas, starting with paths that write or send.
- Tighten schemas: close objects, add bounds and enums, anchor patterns, and enable format assertion only where your validator supports it.
- Add semantic and authorisation checks in the executor, in audit mode first: log what would be rejected for two weeks, review false positives, then enforce. Keep a feature flag to return to audit mode as the rollback path.
- Test adversarially: run indirect injection cases that try to change recipients, paths, amounts and tool choice, and assert rejection. Include chained commands and unanchored pattern cases. See the AI security regression testing guide.
Detection and measurement
Log every proposed call with tool name, arguments, validation outcome per layer, the identity used and the content sources in context. Alert on repeated rejections from one session, which the NCSC notes can indicate an attacker refining an attack, and on approved calls whose arguments include values not seen in the user's request.
- Percentage of action-triggering outputs that pass through a strict schema (target 100).
- Number of tool fields typed as unbounded free-form strings that reach an interpreter (target zero).
- Semantic and authorisation rejection rate per tool, and its trend after model changes.
- Adversarial test pass rate for injection cases per release.
- Share of high-impact actions that required and received human confirmation.
Common failure patterns
- Calling schema validation "injection protection": it removes malformed calls and leaves every valid malicious one.
- Keeping a raw
commandorsqlfield: the most dangerous field is the one that still accepts a grammar. - Relying on constrained decoding alone: generation constraints vary by provider and model; the executor must validate independently.
- Trusting
formatandpatternblindly: unasserted formats and unanchored patterns pass almost anything. - Letting the model supply identity or entitlement: a
user_idorrolefield filled by the model is an authorisation bypass waiting to happen.
Framework mapping
| Framework | Reference | How it applies |
|---|---|---|
| OWASP Top 10 for LLM Applications 2025 | LLM01, LLM05, LLM06 | Injection steers valid calls; output handling per field; excessive agency and complete mediation |
| NIST SP 800-53 Rev 5 | SI-10, SI-15 | Input validation of tool arguments; output filtering before rendering or execution |
| NIST SP 800-53 Rev 5 | AC-3, AC-6, AU-12 | Access enforcement downstream, least privilege for agent identities, logging of calls |
| NIST SP 800-53 Rev 5 | SA-11 | Developer testing, including adversarial tool call tests |
| NIST CSF 2.0 | PR.PS-06, DE.AE-02 | Secure development practice; analysis of rejected and anomalous calls |
| MITRE ATLAS | AML.T0051.001, AML.T0053, AML.T0086, AML.T0101 | Indirect injection, tool invocation, exfiltration and data destruction via tools |
| MITRE ATT&CK | T1059 | Command execution through free text or string fields |
Practitioner checklist
- No model output reaches a shell, query engine, eval or template as an unvalidated string.
- Every action-triggering output is a tool call validated against a closed schema at the executor.
- Schemas set
additionalProperties: false, explicitrequired, bounds and anchored patterns. - Free-form command and query fields have been replaced by narrow tools, enums and typed filters.
- Trust-critical values (payees, recipients, identities) are looked up by the system, not written by the model.
- Downstream systems authorise each call with the end user's delegated identity.
- Privileged tools are withheld while the agent processes untrusted content.
- High-impact actions require confirmation of the exact validated call.
- Tool definitions are pinned and diffed; schema widening goes through security review.
- Adversarial injection tests run on every model or prompt change.
Standards and references
- OWASP: LLM05:2025 Improper Output Handling
- OWASP: LLM06:2025 Excessive Agency
- OWASP Cheat Sheet Series: LLM Prompt Injection Prevention Cheat Sheet
- OWASP Cheat Sheet Series: Input Validation Cheat Sheet
- JSON Schema: Validation Vocabulary, draft 2020-12
- Model Context Protocol: Tools (revision 2026-07-28)
- NCSC: Prompt injection is not SQL injection
Related NHI Mgmt Group resources: OWASP LLM Top 10 LLM05 Improper Output Handling Guide · Prompt Injection Guide · OWASP LLM Top 10 LLM06 Excessive Agency Guide · MCP vs Native Function Calling · Streamable HTTP vs stdio MCP Transport