A Suggestion Is Not an Authorization
The model proposes; structured validation, a permission check and the backend decide. The UI shows only what was actually confirmed.
The intent, the obvious build, and why it breaks
Every lesson starts where the work starts: someone wanted an outcome, and the first implementation that comes to mind has a problem.
The model wants to do something. What has to be true before it happens, and what does the interface owe the user?
Someone asked the assistant to tidy up their account. They expect it to be helpful, and they expect it not to delete anything they would have wanted to keep.
The model returned a tool call. It has the schema and the arguments, so execute it and render the result.
The model is not an authorization system. It has no idea what this user is allowed to do, and it will confidently propose actions they are not permitted to take.
- The model is not an authorization system. It has no idea what this user is allowed to do, and it will confidently propose actions they are not permitted to take.
- Tool arguments are model output, which means they are influenced by whatever the model read — including documents an attacker may control (The Browser Security Model).
- An action taken without a confirmed result renders as done when it may have failed, silently diverging from reality (Optimistic UI).
- Destructive actions executed without a human decision are unrecoverable in exactly the cases where recovery matters.
- Rendering the model's description of what it did, rather than the system's record of what happened, means the interface reports fiction whenever the two differ.
What is actually happening
In the browser, not in the framework.
- The safe path is a chain, and every link is on the server: model proposes → structured validation → permission check → execution → confirmed result → UI. The frontend joins at the last step (What the Frontend Owns in an Agent Product).
- Structured validation means the arguments are parsed against a schema and rejected if they do not fit — not trusted because they arrived in the right shape (Native Validation and Its Limits).
- The permission check runs as the user, not as the agent. The question is always "may this person do this to this object", and it is the same check any other client would face (What the Frontend Is Responsible For in Auth).
- Indirect prompt injection is the reason this cannot be relaxed: text the model retrieved can attempt to instruct it, so a tool call is never evidence of user intent (Cross-Site Scripting).
- The UI's job is to keep proposals and facts visually and semantically distinct, and to put a human decision in front of anything irreversible.
What this makes the browser do
And which of it is avoidable.
- Rendering a proposal as a structured, reviewable summary rather than as prose — more markup, and the difference between reviewable and not.
- Sanitizing every argument value before display; an argument is model output like everything else (Sanitization and Trusted HTML).
- Managing a confirmation dialog with correct focus behaviour (Focus Management).
The chain, and where the frontend joins it
Written out, the safe path is unremarkable — it is the same chain any backend applies to any client request. The only novelty is that the caller is a model, and the temptation is to treat its output as more trustworthy than a request from a browser because it came from your own system.
It did not. It came from a model that read documents. The frontend joins at the last step, and its whole job there is to not overstate what happened.
- 1Model proposes
Emits a tool call with arguments.
fails by Being treated as intent rather than as a suggestion.
- 2Structured validation
Parses arguments against a schema; rejects what does not fit.
fails by Accepting well-formed arguments as evidence of legitimacy.
- 3Permission check
Asks whether this user may do this to this object.
fails by Checking the agent's permissions, or checking at the wrong granularity (Authorization-Aware UI).
- 4Human decision
For anything irreversible, an explicit confirmation on a reviewable summary.
fails by Prose summaries, auto-focused confirm buttons, or confirming everything until it means nothing.
- 5Backend execution
Performs the action and records it.
fails by Executing from the client, where nothing can be enforced.
- 6Confirmed UI state
Renders the backend's record of what happened, with a trace.
fails by Rendering the model's account of what it did (Optimistic UI).
Only the last step is the frontend's. Its contribution to safety is refusing to present anything earlier in the chain as though it were the last step.
Reversibility is the axis that matters
Confirming everything produces confirmation blindness, and confirming nothing produces the incident. The useful sort is not by how important an action sounds but by how easily it can be undone — which is a property you can actually determine, unlike importance.
semantics A modal dialog with aria-modal="true" and an accessible name naming the action and its scope. The confirming control's accessible name states the consequence — "Delete 40 records permanently" — never "OK" or "Confirm".
| Tab / Shift+Tab | Cycles within the dialog only; focus does not escape to the page behind. |
| Escape | Cancels and closes, restoring focus to the control that opened it. |
| Enter | Activates the focused control — which is the cancel or the review target, never the destructive one by default. |
- — Focus moves into the dialog on open, to the dialog container or its heading — not onto the confirming button.
- — Focus is trapped for as long as the dialog is open.
- — Focus returns to the triggering element on close, whether confirmed or cancelled.
- — The dialog's accessible name on open, including the scope: "Confirm: delete 40 records".
- — The full consequence as readable content, not as a colour or icon.
- — The outcome after execution: "40 records deleted", or the failure and what it means.
usually broken by Auto-focusing the destructive button so a stray Enter — from a keystroke aimed at the prompt input — confirms an irreversible action the user never read.
| Action | Reversible? | What the interface owes |
|---|---|---|
| Draft a reply | Fully — nothing left the system | Nothing. Just do it and show the draft. |
| Add a calendar event | Easily, by the user | Show it as done, with a link to undo. |
| Archive 40 records | Yes, but tediously | Summarise the set, show a count, offer bulk undo. |
| Send an email | No — it has left | Explicit confirmation on a reviewable summary, showing recipients. |
| Delete records permanently | No | Explicit confirmation listing what will go, and a server-side authorization check on every id. |
| Spend money | No, or expensively | Confirmation with the amount in the accessible name of the confirming control, plus an audit entry. |
How to build it
Most important first.
- Never execute from the client on the model's say-so. The client requests; the server authorizes and executes (Authorization-Aware UI).
- Classify actions by reversibility, and require an explicit human confirmation for anything that is not easily undone. Reversibility, not "importance", is the useful axis.
- Show the proposal as data — what, to which objects, how many — not as a sentence. "Delete 40 records" reviewed as a list is a decision; as prose it is a leap of faith.
- Render only confirmed outcomes as facts, sourced from the backend's record, with something the user can verify afterwards (Analytics Events That Answer a Question).
- Scope tool credentials to the least they need. A tool that can read one calendar is a smaller problem than one that can read every calendar (What the Frontend Is Responsible For in Auth).
- Make the audit trail reachable from the UI. "What did this thing do on my behalf" should be answerable without asking support.
Keyboard, focus, semantics, announcement
A required field on every lesson in this domain, not a section added when there is room.
- A confirmation for a destructive action is a modal dialog and owes the full contract: correct role, an accessible name, focus moved in, focus trapped while open, Escape to dismiss, and focus restored to the trigger on close (Accessible Component Patterns).
- The consequence must be in the text, not conveyed by a red button. "Delete 40 records permanently" is the accessible name of the confirming control — not "OK" (Contrast, Colour and Motion).
- A proposal must be distinguishable from a completed action non-visually: a heading, a status, real words. If the only difference is colour or an icon, the distinction does not exist for a large number of users (Semantics Before ARIA).
- Never auto-focus a confirming control. A keystroke intended for the input can approve an irreversible action if focus moved under it.
- The outcome — done, failed, partially done — must be announced, since the user may not be watching the region that changed (Live Regions and Announcement).
What can go wrong
- A permission check performed client-side and therefore trivially bypassed by calling the endpoint directly.
- A confirmation dialog that summarises the action inaccurately, so the user approves something other than what runs.
- An action reported as complete because the tool call was dispatched, not because it succeeded.
- Retry repeating a destructive action that already happened (Stopping It, and Trying Again Safely).
- A model-authored link or button rendered with the same affordance as application controls, so the user cannot tell which is which.
- A permission can be revoked between the proposal and the execution, so the check must happen at execution time rather than at proposal time.
- The underlying data can change between the proposal being shown and the user approving it — "delete 40 records" may be 41 by the time it runs, which is why the execution should be bound to a specific set rather than to a filter.
- This is the module's security spine: a model-generated action carries no authority. Treat a tool call exactly as you would an unauthenticated request that happens to be well-formed (The Browser Security Model).
- Indirect prompt injection means a retrieved document can attempt to steer the agent. The defence is not better prompting; it is that the action path is authorized independently of what the model concluded (Prompt Injection in Security Engineering, via
secLinks). - Validate arguments against a schema and against the user's scope. An id in a tool call is a claim about an object, not proof of access to it.
- Render model output as untrusted content everywhere, including inside a confirmation dialog — a proposal is the last place you want an injection sink (Sanitization and Trusted HTML).
- Log what was proposed, what was approved, by whom, and what executed. Without that, "the assistant did it" is unanswerable.
- "The model was told to only do allowed things." Instructions are not enforcement. A system prompt is not an access-control list, and text the model read can argue with it.
- "The tool schema validates it." A schema constrains shape, not permission. Well-formed and allowed are different questions.
- "The user asked for it, so it is authorized." The user asked a question. The model chose an action. Those are not the same event, and the gap between them is where this goes wrong.
- "Confirmations solve it." They help for the irreversible minority and cause blindness if applied to everything. Authorization is the control; confirmation is a second layer.
- "It is an internal tool, so the risk is lower." The injection vector is the content the agent reads, and internal documents are content someone can write.
Measuring it, and what changes in the field
- Rejection rate at the permission check: a non-trivial rate is the system working, and a sudden change is worth investigating.
- Approval rate on confirmations — near-100% suggests the dialog has become a reflex and is no longer a decision.
- Actions executed versus actions the UI reported as executed. Any gap is a correctness bug, not a display bug.
- Undo usage, which tells you which actions actually needed a confirmation and which are friction.
- As the tool set grows, the blast radius of a mistaken action grows with it, and per-tool scoping stops being optional.
- With autonomous multi-step runs, the number of actions between human decisions grows — which is precisely when reversibility classification has to be right.
- In a multi-tenant product, the object-level check is the one that matters, and it is the one most often missing (Authorization-Aware UI).
- Confirmation for irreversible actions adds friction, and that friction is the feature. Classifying by reversibility keeps it off the reversible majority.
- Server-side authorization on every action costs a round trip that a client-side check would avoid, and is the only version that is actually a check.
- Showing proposals as structured data is more work than rendering a sentence, and it is what makes review possible at all.
Where this applies
Frontend advice ages badly and fragments across engines. These labels say what each claim is specific to, and where a different browser, device or framework would differ.
- GENERALThat authorization cannot be delegated to a model follows from the model not being an access-control system and not knowing the caller, so it holds for every vendor, framework and product.
- SPEC-EVOLVINGTool-calling formats, structured-output guarantees and provider-side safety features are changing fast and differ between vendors. Treat every provider-side control as defence in depth on top of your own validation and authorization, never as a replacement for either.
Where the depth lives
This domain teaches the browser-side mechanism and hands the rest off.
- — Software Design — modelling reversibility as a property of an operation rather than a judgement call is what lets this rule be applied consistently instead of case by case.