The direct answer
Treat structured model output as an untrusted proposal: validate its declared schema, then independently check the authenticated actor, resource ownership, current workflow state, and business invariants using trusted application data. A schema-valid object can still request an unauthorized or impossible action. Passing these checks does not itself approve or execute that action.
Start with the boundary between structure and authority
A model can produce a tidy object containing an existing order identifier, a positive amount, and an allowed unit. Those properties do not establish that the caller owns the order or that the amount remains eligible. The failure is accepting a well-formed proposal as an authoritative instruction.
OWASP input validation guidance distinguishes syntax from semantic checks and says authorization requires a separate check. Apply that distinction to model output at the receiving application boundary. Provider formatting features do not supply your application’s ownership rules.
Write a bounded structural contract
For this exercise, define a bounded proposal object with four required fields: order_id, amount, unit, and lines. Identifiers are bounded strings. Amount is an integer from 1 through 10000; unit is either CREDIT_A or CREDIT_B. Lines contains one to four objects, each requiring a bounded line_id and an integer quantity from 1 through 10. Reject unknown properties at both object levels.
Pin the schema dialect and validator configuration. The 2020-12 validation specification defines required fields, types, and numeric or collection bounds. The core specification defines properties and additional properties; merely listing known properties does not reject others. Configure the rejection explicitly for this simple contract.
Limit bytes and nesting before parsing. Reject malformed or over-limit input without passing partially validated data downstream. This contract is a proposed fixture, not a complete schema implementation or an executed validator test.
Give the fixture its own trusted facts
Invent a tenant called Cedar and an authenticated support drafter who may prepare proposals for Cedar orders, but cannot execute refunds. Trusted fixture state assigns ORDER-C7 to Cedar, denominated in CREDIT_A. Its only eligible line is LINE-1, quantity one, worth 6000 units. The remaining eligible amount is 6000; the order is in an eligible state. This fictional rule permits whole-line proposals only. ORDER-M9 belongs to another tenant.
These identifiers, units, quantities, and rules are fabricated. They represent no customer, currency, refund policy, or entitlement. A baseline proposal is {"order_id":"ORDER-C7","amount":6000,"unit":"CREDIT_A","lines":[{"line_id":"LINE-1","quantity":1}]}. Each matrix variation changes only the stated fields or trusted state.
| Variation | Shape result | Independent rule | Expected response |
|---|---|---|---|
| Baseline proposal | Valid | Matches current fixture facts | Ready for permitted review; no execution |
| order_id is ORDER-M9 | Valid | Outside actor’s authorized tenant | Reject without revealing that order |
| amount is 8000 | Valid | Exceeds remaining eligible 6000 | Reject amount; require revised proposal |
| unit is CREDIT_B | Valid | Order uses CREDIT_A | Reject unit mismatch; no conversion |
| quantity is 2; amount remains 6000 | Valid | Only one unit is eligible | Reject quantity and inconsistent total |
| amount is 4500 | Valid | Whole-line rule requires 6000 | Reject unsupported partial amount |
| Baseline after eligibility is revoked | Valid | Current state no longer permits proposal | Stop and refresh trusted state |
| Add approved: true | Invalid | Unknown field is prohibited | Reject shape; never infer approval |
Recompute business facts instead of accepting model assertions
OWASP business logic guidance recommends deriving security-relevant values on the server, checking ownership, and validating combinations against the intended process. In this fixture, obtain actor and tenant from authenticated application context, then load only resources that actor may use. A model-supplied tenant, role, approval flag, or explanation cannot replace that check.
Calculate the eligible quantity and total from trusted line records and the applicable rule version. Compare the proposed amount and unit with that calculation. Reject duplicate line identifiers rather than counting the same entitlement twice. An allowed integer range is a structural bound, not an order-specific allowance.
If the trusted lookup or rule service is unavailable, hold the proposal. Asking the model to confirm its own amount adds no independent evidence. A reviewer should receive the applicable rule and safe mismatch reason, not a persuasive rationale generated from the rejected object.
Return an actionable rejection without silently repairing it
Distinguish correction from permission. A missing quantity can receive a field-level validation error. An authorized order with an amount mismatch can receive “The proposal does not match the eligible line total; revise it for review.” An unauthorized reference should receive a neutral access failure, without another tenant’s details.
Do not silently convert units, clip an excessive amount, discard extra lines, or change the target order and continue. Such changes create a different proposal. Present any suggested correction for explicit reconsideration and validate the resulting object again. Keep a minimal protected decision record with rule version and failure category, avoiding full model payloads or customer details.
Keep validation current and test the proposed boundary
A successful check describes a particular actor, object, rule version, and state snapshot. It is neither approval nor a reservation of eligibility. Before any later authorized effect, the responsible application must recheck current restrictions and enforce its state transition consistently. This guide does not specify a refund execution API, payment process, or callback deduplication mechanism.
For a proposed local test, use only fabricated records, a fixed actor context, and an adapter that records attempted effects without performing them. Write expected outcomes before running the matrix. Check both the valid baseline and every schema-valid rejection; blanket refusal is not a useful validator. Also propose a lookup-unavailable case and a state change between review and attempted execution.
These checks are UNRUN. No schema validator, model, refund API, real money, or customer data was used for this worksheet. Boundary is a prelaunch commercial model router with planned request-path policies; this article does not describe a shipped business-rule validator or refund system.
Primary sources
Official references supporting the guidance. Examples in this guide are synthetic and are not reports of a production incident.