Structured Output Validation Is Not Authorization

August 10, 2026 mcp eu-ai-act agent-security structured-outputs

Every major AI provider now supports structured outputs. Anthropic tool use, OpenAI function calling, Mistral tool JSON, Google Gemini function declarations — they all return agent decisions as typed, schema-constrained data. In the past 18 months, Pydantic validators, Zod schemas, and JSON Schema definitions have become standard fixtures in agent codebases.

This is a genuine improvement. It eliminated a category of parsing errors. It made agent outputs predictable.

It also created a systematic confusion that is showing up in compliance audits.

What schema validation actually checks

When you define a Pydantic model like this:

class AgentAction(BaseModel):
    action: Literal["read", "write", "delete", "transfer"]
    target_resource: str
    amount: Optional[float] = None
    destination: Optional[str] = None

And your agent returns a validated instance of this model, the validation tells you:

  • action is one of the four permitted string values
  • target_resource is a string
  • amount, if present, is a float
  • destination, if present, is a string

That is all it tells you. It says nothing about:

  • Whether this agent is authorized to perform delete on target_resource
  • Whether amount=50000.0 with destination="external-account-id" is within the agent's operational scope
  • Whether the combination of action=transfer and destination is permitted in the current session context
  • Whether the acting agent's identity has been verified against anything

Schema validation is a type check. Authorization is a policy check. These are orthogonal operations. The tooling ecosystem treats them as if they are the same thing, and that conflation is the problem.

How this creates an exploitable gap

Consider a customer support agent with tool access to a billing system. Its MCP server exposes a process_refund tool. The tool schema requires:

{
  "customer_id": "string",
  "amount": "number",
  "reason": "string"
}

The agent's structured output validates. The schema check passes. The tool call goes through.

What nobody checked: this session's authorization covers refunds up to €200. The agent issued a €4,800 refund. The schema is valid. The authorization was not.

This is not a theoretical scenario. It is the direct consequence of building authorization logic into schema definitions — which is what most current implementations do. The action field being typed as Literal["read", "write", "delete"] looks like authorization. It is a format constraint.

A compromised agent, a prompt injection in a retrieved data record, or a miscalibrated model can all produce valid structured output for completely unauthorized actions. The schema validator will pass every one of them.

Free tier: 500 proofs/month, no credit card required.

See plans & get free key

The EU AI Act framing

Article 9 of the EU AI Act requires that high-risk AI systems implement risk management measures proportionate to identified risks. Article 9(2)(b) calls for testing and validation procedures; Article 9(4) requires that risk management address foreseeable misuse.

Schema validation does not constitute a risk management measure under this framing. It is an interface contract. An audit trail showing "we validate all agent outputs against JSON Schema" demonstrates type safety, not behavioral control.

Article 17 (quality management) compounds the problem. If your quality management documentation states "agent outputs are validated before execution," and your validation is schema-only, the documentation implies a level of behavioral control that does not exist. That gap between documented capability and actual capability is where liability accrues.

Regulators reviewing these systems are increasingly clear on this distinction. Validation pass logs tell them the format was correct. They say nothing about whether the actions were authorized, within scope, or constrained to what the risk assessment anticipated.

The three layers that need to stay separate

Robust agent authorization requires keeping three concerns explicitly distinct:

Layer 1 — Schema validation: does the output conform to the expected structure? This is what Pydantic, Zod, and JSON Schema provide. Necessary, not sufficient. Runs at parse time.

Layer 2 — Policy enforcement: is this specific combination of action, target, parameters, and context within the authorized scope for this agent, in this session, at this moment? This requires a policy engine, not a type checker. Runs at authorization time.

Layer 3 — Execution attestation: can you produce cryptographic proof that the authorized parameters are identical to what was executed? This requires signing the approved payload before it reaches the tool. Runs at execution time.

Most current systems have Layer 1. Some have partial Layer 2, typically hard-coded business logic rather than a separable policy engine. Almost none have Layer 3.

Article 9 compliance targets Layer 2 and Layer 3. Schema validation at Layer 1, however thorough, does not substitute for either.

What policy enforcement at the value level looks like

A minimal policy enforcement layer for the billing agent example evaluates:

def authorize_action(action: AgentAction, ctx: SessionContext) -> AuthorizationResult:
    if action.action == "transfer" and action.amount > ctx.max_transfer_amount:
        return AuthorizationResult.deny(
            reason="transfer_amount_exceeds_session_limit",
            limit=ctx.max_transfer_amount,
            requested=action.amount
        )
    if action.destination not in ctx.authorized_destinations:
        return AuthorizationResult.deny(
            reason="destination_not_in_session_allowlist"
        )
    return AuthorizationResult.allow()

This is policy logic, not schema logic. The distinction matters operationally:

  • Policy logic can be versioned and audited independently of tool definitions
  • Policy decisions can be logged with the session context that produced them
  • Policy denials are visible, structured failures that create audit records; schema failures are parse errors typically treated as noise
  • Policy logic can be updated without touching tool schemas or model prompts

Separating these concerns also makes the system testable in isolation. You can run your policy engine against a library of action/context combinations without involving a model at all. That's the kind of validation Article 9 actually requires.

Binding authorization to execution

Separating policy from schema is necessary but not sufficient if you cannot prove that the authorized parameters were actually what executed.

The gap: your policy engine approves {"action": "delete", "target": "record-123"}. Your serialization layer, your network transport, your MCP implementation, and your tool handler all process that payload before it runs. Any layer can modify the parameters. Without a cryptographic binding between the authorization event and the execution event, you cannot prove they carried identical data.

At authorization time, sign the approved payload — action, parameters, session ID, timestamp, and the policy version that approved it. At execution time, verify the signature before the tool runs. The receipt proves that authorization and execution operated on the same data. Without it, you have an authorization log and an execution log with no provable link between them.

This is the architecture ArkForge's Trust Layer implements: authorization produces a signed assertion, the tool wrapper verifies it before execution, and the receipt is recorded with the outcome. Schema validation still happens at input parsing. Authorization decisions and execution proofs are separate, signed, and independently auditable.

What to look for when reviewing agent systems

Schema validation documentation is not evidence of authorization controls. When reviewing an agent system against Article 9 requirements, ask:

  1. Where is the policy engine that evaluates authorization at the value level, separately from schema validation?
  2. What is the session context model — how are per-session limits, scope, and authorized targets represented at runtime?
  3. Is there a cryptographic binding between the authorization decision and the execution payload?
  4. What does a policy denial look like in the audit trail, and is it distinguishable from a schema error?

If the answer to any of these is "we validate against JSON Schema," you are looking at a system with format controls and no authorization controls. The tooling makes schema validation so prominent that it is easy to mistake for something it is not.

Schema validation is table stakes for agent reliability. It is not an authorization mechanism, and the distinction will be tested — either in an audit or in an incident.


Prove it happened. Cryptographically.

ArkForge generates independent, verifiable proofs for every API call your agents make. Free tier included.

Compare plans → or get free key directly