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.

Original proposed refund-proposal matrix; UNRUN
VariationShape resultIndependent ruleExpected response
Baseline proposalValidMatches current fixture factsReady for permitted review; no execution
order_id is ORDER-M9ValidOutside actor’s authorized tenantReject without revealing that order
amount is 8000ValidExceeds remaining eligible 6000Reject amount; require revised proposal
unit is CREDIT_BValidOrder uses CREDIT_AReject unit mismatch; no conversion
quantity is 2; amount remains 6000ValidOnly one unit is eligibleReject quantity and inconsistent total
amount is 4500ValidWhole-line rule requires 6000Reject unsupported partial amount
Baseline after eligibility is revokedValidCurrent state no longer permits proposalStop and refresh trusted state
Add approved: trueInvalidUnknown field is prohibitedReject 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.

  1. JSON Schema 2020-12 Validation
  2. JSON Schema 2020-12 Core
  3. OWASP Input Validation Cheat Sheet
  4. OWASP Business Logic Security Cheat Sheet