What an agent approval actually approves: effect binding and trusted cards
A new preprint audits ten agent runtimes and finds approvals that don't bind to what executes. The findings, the proposed fix, and what to ask of your tools.
Approval steps are now common: the paper counts them in 15 of 21 production agent systems it surveyed. Before a risky tool call, the run pauses, a person or a policy says yes, and the call goes ahead. The question a new preprint asks is simple: yes to what, exactly?
The paper, submitted to arXiv on 15 October 2026 by three researchers at SportsVision AI, argues that an approval only protects you if two things hold. The agent can't produce the approval itself, and the approval commits to exactly the action that then runs. The authors test that against ten agent runtimes and find the second condition often fails. It is a preprint and hasn't been peer reviewed, but the audit is specific enough to check against your own stack.
What the audit found
The authors ran nine runtimes and reviewed one more statically. They didn't test proprietary coding agents such as Claude Code, Codex CLI or Cursor, so the results describe agent frameworks, not those tools.
The central test is effect binding: after approval, can anything change what executes? Of the eight setups where they could measure it, three passed (the OpenAI Agents SDK, the AutoGen code-executor approval and the MCP Python SDK), Google ADK was partial, and four failed. LangGraph 1.2.15 executes whatever arguments are in the run's state at resume time, so whoever can resume the run can change the action. In Agno 3.1.3, the authors could swap the arguments through the documented remote continue endpoint. Pydantic AI 2.56.0 executes the arguments in a message history the caller supplies. In CrewAI, a later hook can change arguments an earlier hook approved, which the authors rate as a low-severity gap.
Three further findings apply more widely:
- Floods aren't limited. Wherever the authors could measure it, seven setups in all, there was no cap or rate limit on how many approvals could be pending. In the five runtimes with a native pause, one model turn raised 20 pending approvals. If a person approves in bulk, the request that matters can hide among routine ones.
- Single use depends on run state. In five of the audited setups, an approval was consumed only relative to the saved run state. Roll that state back and the approved action runs twice.
- No verifiable record. As far as the authors can tell from the APIs, none of the runtimes leaves behind a signed record of an approval that someone else could verify afterwards. Approvals are stored as unsigned flags in run state.
The paper also notes that the Agno substitution reproduces on 3.0.10 and 3.1.3, beyond the version range of an earlier published report on the same flaw. The authors describe their results as findings about designs, not as vulnerabilities in deployed services, and say they didn't contact the maintainers. Expect some of them to be disputed or fixed in the coming weeks. They add that several of these gaps can be closed by a team that shows the arguments faithfully, ties each stored approval to a nonce and only lets a trusted service resume runs.
Why the card matters
Even a well-bound approval fails if the approver can't see what they are approving. The paper's attacks include decoy recipients, amounts that cancel out, padding that pushes the important part off screen, and spoofed labels.
It uses MCP elicitation, where a server can ask the user for input during a call, as a concrete case. In form mode the client shows the server's message as written. The authors found that a message padded with blank characters and a right-to-left override was passed to the client unchanged by the SDK, leaving it to the client how to display it, and that two different payments, 10 to one account and 10,000 to another, produced identical requests. The elicitation specification already anticipates part of this. Servers "MUST NOT use form mode elicitation to request sensitive information" and must use URL mode for those interactions instead. It also says clients should rate-limit requests and give users clear approval controls. The paper's point is that a client which follows the letter of form mode can still show a misleading request.
The proposed fix
The paper's reference design builds an approval from four parts:
- A canonical description of the effect, such as the recipient, amount and tool, hashed with SHA-256 over a fixed JSON form.
- A confirmation card rendered from that description, not from text the agent wrote.
- A signature over the hash from a device the agent can't reach. The authors used a passkey.
- An executor that runs only the stored description the signature covers.
This is close to what Microsoft's Agent Governance Toolkit sets out in ADR 0030. That design computes an action digest over a canonical JSON form (RFC 8785) and checks it again at execution time, together with the policy version. For irreversible actions the approval is consumed in the same transaction as the action. It also states that a model's recommendation can't satisfy a human approval stage, and that a timeout fails closed. The paper tested ADR 0030 as one of its targets, though: it found a parity issue in how floats are canonicalised, and noted that the ADR doesn't claim flood control (the module let 50 approvals pile up).
The measured results are useful mainly for what they say about where protection comes from:
- The card does most of the work. In AgentDojo scenarios with forged narration, attack success against an approver that compares the card with the task fell from 0.54 with an agent-narrated card to 0.03 with a card rendered from the effect description. What was left of the 0.03 came only from actions the policy let through without an approval.
- Model approvers see better on a trusted card. Across 15 language models acting as approvers, the share of attacks caught rose from 0.30 with narrated text to 0.98 with the trusted card. On the trusted card the models also refused 16% of harmless near misses, which the authors trace mostly to placeholder text in their test material. Without those cases the rate would be 0.02.
- The second device added nothing measurable over a trusted confirmation on the same machine, in their setup.
- Flooding still worked 0.45 to 0.60 of the time with limits on, against a modelled human approver.
The authors are explicit about limits. Their approvers are simulated, with no human-subjects data, and in their words "approval cannot stop effects that policy already allows". If a tool call doesn't need approval, none of this applies to it.
What it means for engineering and security leaders
An approval prompt is a control only if you know what it binds to. Ask that of every agent framework and tool your teams build on:
- What is approved? The tool name, the full arguments, or a model's summary of them? If it's a summary, the approver is trusting the agent's description of its own action.
- Can the arguments change after approval? Resume APIs, hooks, middleware and editable history are the places to look.
- Is an approval single use across retries, rollbacks and restored sessions?
- Is there a limit on pending approvals per run, and does a burst of them raise an alert?
- Where is the approval recorded, and could someone outside the agent verify it later?
The paper also reframes what approvals are for. They are a narrow control for a small number of high-impact actions. Wide tool permissions with frequent prompts give you approval fatigue without much protection. Our article on unpatched approval bypasses in Roo Code shows what happens when the prompt itself can be skipped.
Practical takeaways
- If you use LangGraph, Agno, Pydantic AI or CrewAI approval flows, check whether your code re-reads arguments from state that can be edited between approval and execution. Re-derive and compare them before running the tool.
- Render approval screens from structured arguments, not from text the model wrote, and show the exact values for recipients, amounts, paths and commands.
- Strip or flag invisible and bidirectional control characters in anything shown to an approver.
- Make irreversible actions consume their approval atomically, so a retry or rollback can't replay it.
- Cap approvals per run and per user, and treat a burst as a signal.
- Keep approvals for actions that matter. Make everything else either allowed or denied by policy.
SpanDock's MCP gateway handles approval outside the AI tool. A tool call that needs approval doesn't run. The AI tool is told the call is waiting, and an administrator reviews it on the Approvals page, which shows who asked, which tool, and the arguments with secrets removed. Approve once lets exactly that call through, once, within 24 hours, and a denied call stays denied. The gateway is on the Premium and Enterprise plans.