You ask an agent to tighten the wording of one paragraph in a document you own. It reads the paragraph, drafts a replacement and shows it to you. You read the draft and press approve. A few minutes later the agent submits the change and the document now says what you approved.
That sequence feels settled the moment you press approve. It is not. The approval is a fact about what you looked at, at one moment. The write happens later, in a different component, on whatever the document has become by then, using whatever request the agent actually sends. Between the two, several things can move. Someone else can edit the paragraph. The agent can submit different text. The request can be aimed somewhere else. A copy of whatever let the agent act can end up in other hands. Your permission can be withdrawn. Whether any of that matters depends entirely on what the component that performs the write checks, at the moment it writes.
Watch the explanation
3:45 · narrated · optional captionsPlay when you’re ready; captions are available in the player. Open the 4K film ↗
So the useful question is narrower than “is the agent trustworthy?” and more practical than “is this secure?”. It is: when the change is written, what has to be true for it to carry your authority, and for nothing else to? This article answers it by following one small edit through every way it can go wrong, and the checks that stop each one.
One small edit
An invented example. The people, the documents, the service and every line of text below are made up for teaching. They depict no real system, product, user or incident. The rules that decide each outcome are small enough to state in full in the technical companion, and the interactive explainer computes them live.
Ines keeps field-notes, a short write-up of a spring bird count along one stretch of river. It has three sections: Purpose, Method and Limits. The Limits section currently reads:
“Counts were taken from the north bank only, so results may undercount species that keep to the far shore.”
She asks a drafting agent to make it crisper. The agent proposes:
“We counted from the north bank only. Species that stay near the far shore may be undercounted.”
Three other parties matter. Tomas co-edits field-notes with his own rights and can change it at any time, without involving the agent. A second document, team-handbook, has its own Limits section (“Team members must not publish raw survey locations.”); Ines may read it but not change it. And a document service stores both documents and performs every write. It has its own storage privilege over everything it stores, which is how it can write anything at all. That fact will matter.
The story runs on a short clock. At 10:00 Ines asks for the edit and hands the agent a narrow grant of authority. At 10:01 she gives it a copy of the document to draft from. At 10:02 it drafts. At 10:04 she approves the exact change. At 10:06 the agent submits it. The grant expires at 10:15. The interesting things happen at 10:05, when Tomas might edit Limits or Ines might change her mind.
- OwnerInes asks for one edit, delegates a narrow grant and later approves one exact change.
- GrantA credential that admits one operation: replace Limits in field-notes, until 10:15, presented with the agent’s key.
- AgentDrafts from Ines’s copy, then submits the change with the grant.
- Enforcement pointThe document service checks the request against the grant, Ines’s rights, the approval and the current text, in the same step as the write.
- DocumentChanges only if every check passes. A record of the decision is written afterwards.
Five questions that sound like one
“Was the agent allowed to do that?” bundles several questions that are answered in different places, by different evidence. Keeping them apart is most of the work.
- Identity: who, or which key, is presenting this request?
- Scope: what may this delegate do, to which object, on whose behalf, until when?
- Approval: did the responsible person agree to this change, to this version of the text?
- Provenance: the record of who asked, who acted, under which grant and approval, and what was decided.
- Enforcement: which component actually refuses, and which of the above does it consult?
Each can be present while another is missing. An agent can be perfectly identified and still be asking for something outside its scope. A request can be inside scope and still not be the change anyone approved. A beautiful record of who approved what protects nothing if the service writes without reading it. The example is built so that each of these can be broken on its own and its consequence seen.
Hand over less than you hold
Ines could simply let the agent act as her: use her signed-in session, with all of her rights over both documents. Computer-security research has a name for authority used that way. Miller, Yee and Shapiro call it ambient authority, authority “exercised, but not selected, by its user” (Capability Myths Demolished, 2003). The problem is not that Ines’s rights are large. It is that nothing about the request picks which of them it means to use.
Instead she hands over a grant that has been narrowed, one restriction at a time, until it admits exactly the operation she wants done. The model counts operations as a document, a section and an action (read, replace, delete or share), so there are 24 in all. Ines may perform 15 of them: everything on field-notes, plus reading the three handbook sections. Each restriction added to the grant shrinks what it admits:
field-notes12replace_section3Limits1The final grant admits one operation: replace the Limits section of field-notes. It does not even admit reading the document, which is why Ines hands the agent a copy to draft from. The grant governs the later write and nothing else.
Two properties make this useful. First, it can only narrow. The construction the model borrows from macaroons (Birgisson and colleagues, 2014) chains each restriction into a keyed signature. Anyone holding the grant can add a restriction, but nobody can remove one without breaking the signature, and adding a contradictory one (“only section Purpose”) leaves a grant that admits nothing, because every restriction must hold. Second, it never exceeds the person delegating it. That rule is old: Dennis and Van Horn’s 1966 design for protected computations already required that a grant cannot pass on authority the granting sphere does not itself hold.
Narrowing is not automatic in today’s web standards either. OAuth scopes are strings that each authorization server defines for itself (RFC 6749, §3.3). Token exchange lets a service obtain a new token to act for someone, but the standard does not require the new token to be narrower; that is the issuer’s policy (RFC 8693). And for structured requests such as “write access to this one file”, there is “no standardized mechanism to compare two arbitrary authorization detail requests” (RFC 9396, §6.1). Narrower is something a system has to define and check, not something a protocol hands you.
Approve the change, not the name
A grant bounds what the agent may do. It says nothing about whether Ines agreed to the particular words the agent will write. That is the approval’s job, and the most important design choice in the example is what the approval names.
The weak form names a place: “the agent may change Limits in field-notes.” The strong form names a change: this target, starting from this exact text, replaced by this exact text.
- target
- field-notes, section Limits
- base
- the text Ines’s copy contained:
Counts were taken from the north bank only, so results may undercount species that keep to the far shore.
(version 578a7f7887eb) - new
- the agent’s proposal:
We counted from the north bank only. Species that stay near the far shore may be undercounted.
(version dff0f4124f04) - approval
- a digest of those three, recorded by the service when Ines approves at 10:04 (6306ea2c7247)
A digest identifies bytes. It is not a signature, and it does not by itself show that Ines agreed to anything. In the model, that trust comes from somewhere else: Ines approves while signed in to the service, and the service writes the approved tuple into its own protected state, where the agent cannot replace it. The digest is how the service later recognises whether a request matches what was recorded. The model assumes this approval path rather than simulating a login, and it assumes that what Ines was shown is what was hashed.
Standards work is converging on the same principle for agents. An IETF Internet-Draft on agent identity, draft-ietf-wimse-aims (version 00, last updated 15 September 2026, intended status Informational; a draft, not a standard), says that the confirmations agent frameworks ask for during a task “do not by themselves constitute authorization and MUST be bound to a verifiable authorization grant”. A tap on a screen shows that someone agreed to something. It becomes authority only once it is bound to what they agreed to, and checked where the action happens.
Nine checks at the moment of the write
At 10:06 the agent submits the change. The document service is the only component that can refuse it, so this is where everything above either counts or doesn’t. In the example the service runs nine checks, always in the same order, and reports every one as passed, failed or not checked, so a refusal always says why.
| Check | What the service asks | Which question it answers |
|---|---|---|
| C1 integrity | Does the grant’s chain of restrictions verify, unaltered? | Is this delegated authority intact? |
| C2 audience | Was the grant issued for this service? | Scope: where |
| C3 presentation | If the grant is bound to a key, did the presenter prove possession of it? | Identity: which key presents |
| C4 time | Is it before the grant expires? | Scope: until when |
| C5 not revoked | Has Ines withdrawn the grant? | Is the authority still current? |
| C6 scope | Is every write inside the grant: document, section, action? | Scope: what |
| C7 delegator | Could Ines herself make every one of these writes, right now? | No more than the delegator holds |
| C8 version | Is the section’s current text the base the approval named, checked in the same step as the write? | Is this the artifact that was approved? |
| C9 approval | Is the submitted change the change Ines approved? | Approval: this exact change |
| record | Who asked, who acted, under which grant and approval, what was decided | Provenance: written after the decision, not read by it |
On the plain path all nine pass. Limits becomes the agent’s proposal, the handbook is untouched, and the service writes a record: on behalf of Ines, by the drafting agent, key proof verified, under the recorded approval, allowed. Everything interesting happens when one input moves.
The requirement that every check run at the write, not earlier, comes from one of the oldest principles in the field. Saltzer and Schroeder’s 1975 design principles call it complete mediation: “Every access to every object must be checked for authority.” In the same passage they warn that proposals to gain performance by remembering the result of an authority check should be “examined skeptically. If a change in authority occurs, such remembered results must be systematically updated.” An approval is exactly such a remembered result: Ines agreed at 10:04. The rest of this article is about the two ways it can go stale before 10:06.
When the document changes after approval
At 10:05, unaware of the agent, Tomas edits Limits to add a detail:
“Counts were taken from the north bank only (two visits, May), so results may undercount species that keep to the far shore.”
At 10:06 the agent submits the approved change, word for word. It is still inside the grant, still in time, still presented with the agent’s key, still exactly what Ines approved. One check fails: C8. The section no longer holds the text the approval was given against. The service refuses, and Tomas’s text survives.
That is the right outcome, and it is worth seeing why. Ines approved replacing a specific text with another. That text no longer exists. Applying the approved replacement now would not be carrying out her decision; it would be silently deleting Tomas’s, which she never saw. The honest next step is the tedious one: re-read the section, redraft, re-approve.
The web already has this mechanism for a different purpose. HTTP’s If-Match precondition lets a client say “apply this only if the resource is still the version I read”, and a server that finds the condition false “MUST NOT perform the requested method” (RFC 9110, §13.1.1), precisely to prevent lost updates. The example uses the same idea, with one addition: the version being protected is the one the approval named.
The check and the write happen in one step. In the model, C8 and the write are a single indivisible operation. If they are separate, Tomas’s edit can land between them: the check passes, then the write overwrites his text anyway. MITRE describes this time-of-check, time-of-use weakness as one where “the resource’s state can change between the check and the use in a way that invalidates the results of the check” (CWE-367). A shorter gap does not fix it; only making them one operation does. RFC 9110 states what a server must do, and leaves this atomicity to the implementation.
One subtlety: C8 compares text, not history. If Tomas edits Limits and then restores the original exactly before 10:06, C8 passes and the approved change goes through. Here that is arguably correct, because the agent replaces exactly the text Ines reviewed. But it means the check shows the base is the same, not that nothing happened in between. A system that needs to know about intervening edits would use a revision number that is never reused, which is a different policy.
When the request changes after approval
Now leave the document alone and change what the agent submits. Instead of the approved proposal it sends:
“We counted from the north bank only. Results are complete for all species.”
That is a different claim, and one the survey cannot support. It is still inside the grant (Limits, replace, in time), so C6 passes. C8 passes too, because the section still holds the original text. One check fails: C9. The submitted change is not the approved change. The service refuses, and Limits is unchanged.
Notice what did not catch this: the grant. Scope answers “may the agent replace Limits?”, and the answer is yes. Only a check against the approved content can answer “is this the replacement Ines agreed to?”.
Ask for more than the grant allows and it is the other way round. If the agent also rewrites Purpose (“This survey proves the river is healthy.”), C6 fails because the grant names only Limits, and C8 and C9 fail because the approval described one section and one change. C7 passes: Ines could have rewritten Purpose herself. Her delegate cannot, which is the point of handing over less than you hold.
Approve a name instead
Now make the weak choice. The approval records only “field-notes / Limits”: the agent may change that section. Replay both stories.
Tomas edits first · exact change
DENY · C8 version
Tomas’s text survives. The approval described a version that no longer exists.
Tomas edits first · section name
ALLOW · lost update
The proposal is written over Tomas’s text. Nobody sees his edit disappear.
Agent sends other text · exact change
DENY · C9 approval
Limits is unchanged. The submitted change is not the approved one.
Agent sends other text · section name
ALLOW · unapproved content
“Results are complete for all species.” is written to Limits, in Ines’s name.
With a name-only approval, C8 has no version to compare, and C9 can only confirm that the request is aimed at “Limits of field-notes”, which both the overwritten-edit request and the altered-text request are. Both are allowed. In one, Tomas’s edit vanishes. In the other, text Ines never saw is written under her authority. An approval that names a section accepts whatever that section, or that request, has become.
This should not be over-read. In the model the name-only approval also loses the version check, because the approval is what carried the base version. A different design gives the agent a standing permission to maintain Limits, with no review of wording, but still requires every write to carry the version it was drafted from. Under that design Tomas’s edit is protected (the precondition refuses the write) while the altered text is accepted, because nobody promised to review wording. That is a reasonable choice if Ines trusts the agent’s judgement within Limits. It shows cleanly that the two protections answer different questions: the version check protects other people’s edits; content approval protects the wording. If Ines wants to approve wording, the approval has to name the wording.
Try it: change one input at a time
The interactive explainer runs the same nine checks on every combination of the example’s eight inputs: what the agent submits, whether Tomas edits first, what the approval names, whether the service checks the delegated request, who presents the grant, whether the grant is bound to the agent’s key, when the commit happens, and a note the agent can attach. Its guided route walks the paths in this article; free exploration lets you build the failure you want to see, and “Return to my example” brings back your own settings after the guided route.
The service that writes with its own privilege
So far the service has always consulted the grant and the approval. Suppose it doesn’t.
In 1988 Norm Hardy described a compiler that held a licence to write files in its own home directory, where it also kept a billing file. Users told it where to write its debugging output. One user named the billing file. The compiler wrote there using its own licence, and the billing file was overwritten. In Hardy’s words, the compiler “serves two masters and carries some authority from each to perform its respective duties. It has no way to keep them apart.” The name came from the user; the authority to write came from the compiler’s own licence; nothing tied the two together. His remedy was a single thing that does both jobs: “The capability both identifies the file and authorizes the compiler to write there.”
The document service is in the compiler’s position. It has its own privilege to write every document it stores. Now let the agent aim the approved text at team-handbook’s Limits instead, and switch the service into a mode where, after checking the grant’s signature, audience, key, expiry and revocation, it lets the request choose the target and writes with its own privilege, without checking scope, Ines’s rights or the approval. The write is allowed. The handbook’s rule about survey locations is replaced by the field-notes proposal, although Ines can only read that document. Her delegate has done something she could not.
Switch the service back to checking the delegated request and the same request fails four checks: C6 (outside the grant), C7 (outside Ines’s own rights), and C8 and C9 (not the approved target). Nothing about the service’s credentials changed between the two runs. Only what it checked did.
The same switch undoes the version protection. With an exact approval recorded but a service that never reads it, Tomas’s edit is lost exactly as it was under a name-only approval. An exact approval protects the write only when the enforcement point consults it.
Two cautions keep this from turning into a slogan. First, the fix is not “services should not have privileges”; a storage service has to be able to write. The fix is that it checks, at use, that the request it serves stays within what its requester was given. An access-list or attribute-based guard that evaluates the same conditions on every request enforces this example’s policy perfectly well; nothing here needs a capability architecture. Second, capabilities are not a guarantee either. Miller, Yee and Shapiro put it carefully: “Eliminating ambient authority helps make it possible to avoid confused deputies, but doesn’t guarantee that deputies will never be confused.” Current guidance for LLM applications lands in the same place. OWASP’s entry on Excessive Agency recommends minimal permissions, acting in the context of the specific user, human approval for high-impact actions, and enforcing authorization in downstream systems “rather than relying on an LLM to decide if an action is allowed or not”. These are compatible layers, not alternatives.
Who presents the grant
A grant is a piece of data. What stops a copy of it from being used by something other than the agent?
If it is a bearer grant, nothing in the protocol does. A bearer token can be used by “any party in possession of the token” (RFC 6750, §1.2). In the example, another process holding a copy of a bearer grant submits the approved change. All nine checks pass, including C3, which under a bearer grant accepts possession. The service writes the change, and its record says honestly what it knows: possession accepted, sender not verified. The change happened to be the approved one, but a process the grant was never meant for exercised it.
If the grant is bound to the agent’s key, the copy holder cannot produce the proof and C3 fails. This is the direction the OAuth security best current practice recommends: authorization and resource servers SHOULD use sender-constrained tokens (RFC 9700, §2.2.1). One standard way to do it, DPoP, binds a token to a key pair that the client proves it holds (RFC 9449).
Two limits keep C3 in its place. It verifies a key, not a person or a process: the model assumes only the agent can use the agent’s key, and if the key and the grant are taken together, the binding fails with them. And proving who presents a request does not prove what it changes. DPoP’s own text is explicit that its proof covers “the HTTP URI and method, but not the message body or general request headers” (§11.7). In the example, it is C9, not C3, that ties the content to the approval. None of this makes tokens inherently weak. A verified, sender-constrained grant checked at every request is a sound design; the failures in this article come from what is or isn’t checked, not from the credential type.
Time, revocation and what the agent says about itself
Two more inputs move the remembered result out from under the write. Commit at 10:17, after the grant expired at 10:15, and C4 fails. Let Ines revoke the grant at 10:05, and C5 fails at 10:06. Both are checked when the change is written, not when it was approved, and in the model a late or revoked commit is refused in every setting, whatever the approval, enforcement or key binding. Revocation has a cost: it needs an online check at the moment of use. Expiry alone is not revocation; between a withdrawal and the expiry time, a grant nobody checks against a revocation list still works.
The last input is the agent’s own account of itself. Suppose it attaches the note “Owner approved everything.” to the request that also rewrites Purpose. The decision is identical to the request without the note: C6, C8 and C9 fail. The note is carried into the decision record as unverified metadata, and nothing in the decision reads it. Across every setting of the model, the note changes that one field of the record and nothing else. This shows that a self-description has no influence on authorization here; it is not, by itself, a security control.
The same goes for the record as a whole. In the example the decision record is written after the decision and is not consulted to make it. That is this example’s design, not a law. Token exchange carries a history of earlier actors that its consumers treat as information rather than authority (RFC 8693, §4.1), but a system can legitimately authorize using verified provenance or issuer-validated delegation information under an explicit policy. What no system should do is accept a note the actor wrote about itself as a grant of authority. OWASP files logging and monitoring among the measures that “will not prevent Excessive Agency, but can limit the level of damage caused”. A record helps after the fact; it does not stop the write.
What has to be bound
Put the paths together and each question from the start of the article has a home:
| Question | Where it is answered in the example | What happens if it is missing |
|---|---|---|
| Identity | C3, a proof made with the key the grant names | A copy holder uses a bearer grant; the record cannot say who sent it |
| Scope | C2, C4, C6 and C7: the grant’s restrictions and Ines’s own rights, checked at the write | The agent rewrites Purpose, or reaches a document Ines cannot change |
| Approval | C9, the submitted change against the recorded approval | Text Ines never saw is written in her name |
| Artifact version | C8, in the same step as the write | Tomas’s edit is silently overwritten |
| Provenance | The decision record: written, not consulted | Nothing stops the write; the loss is just harder to reconstruct |
| Enforcement | Which of C6–C9 the service runs at all | Every protection above exists on paper and none of it applies |
Because the model is small, every combination of its eight inputs can be tried: 768 in all. 126 are allowed, and 110 of those cause at least one of the harms above. In the configuration where the approval names the exact change, the service checks the delegated request and the grant is bound to the agent’s key, 2 of the 96 settings are allowed, and neither causes harm. Weaken one choice at a time and specific harms return: name-only approval, 6 harmful settings (lost updates and unapproved content); a service that bypasses the request checks, 14 (all four content and scope harms); a bearer grant, 2 (use by another process). These are counts of the model’s possible settings, not measurements. They say nothing about how often anything happens in a real system, and “no harm” holds only inside the model’s assumptions.
Where this stops being enough
The example is deliberately small, and its useful limits are the places a real system has to do more.
- Re-approval churn. Binding approval to a version means any concurrent edit invalidates it. In a busy document that becomes repeated redrafting and re-approval. Finer targets (a paragraph rather than a section) reduce collisions; a merge-aware approval, approving a diff that still applies cleanly, needs a defined merge contract, which then becomes part of what is approved.
- Referenced material. A digest of a change does not cover what the change points to. If an approved configuration names a script, and the script can still be edited afterwards, approving the configuration did not approve the bytes that will later run. The binding has to extend to the referenced material, or the material has to be copied immutably at approval. The document example avoids references on purpose, and no standard read for this piece prescribes a general recipe.
- What the person saw. The digest binds whatever was hashed. If the diff on Ines’s screen is not the diff that was hashed, through rendering, whitespace or look-alike characters, the approval binds the wrong thing.
- Attention. Binding makes each approval mean something specific. It does not make anyone read it carefully, and finer approvals mean more prompts.
- The enforcement point is trusted. If the service is buggy or compromised, every check is moot. Complete mediation assumes a correct mediator.
- One service, one write. Real changes span several resources, each of which has to enforce its own part. Audience restriction keeps a grant from being replayed at the wrong service (RFC 8707); it says nothing about which operation is allowed inside the right one.
The same binding question appears at larger scale in Loki’s Era 3 design notes, whose capability map (a draft last updated on 29 September 2026) asks how an operator can authorise one specific change rather than open broad access. It is a proposed design: nothing in it is built or tested, and this article makes no claim about how it would answer the question.
None of the ideas here are new. Capabilities, complete mediation and conditional writes are decades old, and today’s OAuth standards supply most of the vocabulary. What the example adds is a place to see them meet at one write. An approval protects you only if the component that makes the change checks the exact change you approved, against the version you approved it on, with authority no wider than yours, at the moment it writes.
Sources
- N. Hardy, “The Confused Deputy (or why capabilities might have been invented)”, ACM SIGOPS Operating Systems Review 22(4), 1988. Reproduction.
- J. B. Dennis and E. C. Van Horn, “Programming Semantics for Multiprogrammed Computations”, Communications of the ACM 9(3), 1966. doi:10.1145/365230.365252.
- M. S. Miller, K.-P. Yee and J. Shapiro, “Capability Myths Demolished”, 2003. PDF.
- J. H. Saltzer and M. D. Schroeder, “The Protection of Information in Computer Systems”, Proceedings of the IEEE 63(9), 1975. Online copy.
- A. Birgisson et al., “Macaroons: Cookies with Contextual Caveats for Decentralized Authorization in the Cloud”, NDSS 2014. Publication page.
- MITRE, CWE-367: Time-of-check Time-of-use (TOCTOU) Race Condition.
- RFC 6749 (OAuth 2.0), RFC 6750 (bearer tokens), RFC 8693 (token exchange), RFC 8707 (resource indicators), RFC 9110 (HTTP semantics), RFC 9396 (rich authorization requests), RFC 9449 (DPoP), RFC 9700 (OAuth security best current practice).
- OWASP GenAI Security Project, LLM06:2025 Excessive Agency.
- IETF WIMSE working group, draft-ietf-wimse-aims-00, Internet-Draft, work in progress.
Standards and web guidance were read on 4 October 2026. Full citations with section numbers, the formal model and the complete decision trace are in the technical companion.
Authorship and contributions
Contributors: Moiré (9 October 2026: site composition under Loki’s direction).