A support chatbot for tenant acme receives a ticket asking for a refund. The caller sends only chat text, yet assist-api lets the model produce a payments.refund request and sends that request using the service’s broad payment credentials. The backend issues a refund proposal that u_123 was never entitled to make. Nothing exotic was required. Untrusted text crossed from a caller message into an action, while the model had no authority to decide whether that action was allowed.
A guardrail is an enforcement point that constrains what an LLM application accepts, spends, returns, or executes. In this failure, the simplest useful guardrail rejects a refund request unless the authenticated principal has the required scope and a server-side approval gate accepts the real refund parameters. Prompt wording cannot provide that protection because the model follows text that reaches it; it does not authenticate callers, hold budgets, or authorize tools.
The running system has one path that we will keep unchanged. End user u_123 belongs to tenant acme and calls assist-api through llm-gateway. The FastAPI service assembles a prompt, calls provider providerA on the chat route using $CHAT_MODEL, and may request a tool action. u_123 holds chat:invoke and payments:refund:propose, not unrestricted payment authority.
The payment contract remains narrow and explicit. The only payment tool is payments.refund, with invoice_id such as inv_987, amount_cents as an integer such as 1299, and reason as a string. A proposed refund is not permission to execute a refund. The server checks tenant ownership, scope, arguments, and approval before any irreversible payment action runs.
Every text-bearing boundary is untrusted, including the caller message, retrieved documents, tool results, and model output. A retrieved support document can contain instructions that conflict with the application’s policy, and model output can contain fields that would become unsafe if passed to a payment API without validation. This is LLM01 Prompt Injection in the OWASP Top 10 for LLM Applications 2025, published by the OWASP GenAI Security Project in November 2024. The invariant is concrete: no tool call may run with more authority than u_123 caused it to have.
The fixed path places the untrusted inputs and enforcement boundaries in one view.
The gateway establishes who called, while assist-api enforces the user’s tenant, scopes, tool contract, and approval requirement. Provider providerA generates output under its own provider credential, but that credential must never become authority to refund on behalf of u_123. Using the service’s broader permissions for a user who lacks them creates a confused deputy.
Boundary rule
The model may suggest
payments.refund; only server-side checks may authorize or execute it.
Because each boundary has a different job, a refusal should identify the stage that could not safely continue. llm-gateway returns 401 Unauthorized for a missing, invalid, revoked, or compromised credential. After authentication, the gateway or application returns 403 Forbidden when a caller lacks a required scope. A blocked destination for a tool fetch also returns 403 Forbidden, and the blocked destination is never contacted.
After providerA responds, assist-api validates model output before a parser, payment API, or other downstream sink receives it. Schema-invalid output returns 422 Unprocessable Content, the RFC 9110 phrase that older documentation calls Unprocessable Entity. Reusing an idempotency key with different refund parameters also returns 422 Unprocessable Content; reusing the same key while the first request remains active returns 409 Conflict.
The remaining outcomes describe an operational boundary rather than a model decision.
202 Accepted returns when an irreversible refund is held for approval, along with a pending-approval identifier.429 Too Many Requests with Retry-After returns when shared quota or a rate limit is exhausted.503 Service Unavailable returns when the enforcement store is unavailable and the application cannot safely enforce policy.504 Gateway Timeout returns when providerA exceeds the provider-call timeout.These outcomes prevent clients from treating a refusal as a vague model failure. They also reveal where to investigate, whether the missing control is identity at llm-gateway, validation in assist-api, or a provider call that did not complete.
In the opening failure, the attacker has only a chat message but causes an irreversible refund proposal. Which stage should refuse first: A, caller authentication and tenant binding at llm-gateway; B, tool authorization and approval at assist-api; or C, output checks after the model?