If two agent actions are identical in every respect except their identifier, can a human approval granted to one of them authorize the other?
For Authority the answer depends on which question is asked. For the INVOKED and RECORDED questions about one specific action, the answer is no: an approval covers exactly the action instance that requested it. One view, which answers a question about the action’s fingerprint rather than about an instance, deliberately still says yes. This article shows both, and why the difference matters.
The scenario
This is a post-audit regression scenario added in commit 46e1321. It was not one of the four findings recorded by the earlier adversarial audit. One canonical event history contains, in this order:
- A root delegation
D1, granted by a human, lets an agent create purchase orders. Amounts between 100 EUR and 1,000 EUR require a human approval. - The agent requests action
A1: a 500 EUR purchase order, invokingD1. - The agent requests action
A2: a distinct action with the same requester, the same capability, the same parameters, therefore the same fingerprint, and the same invoked delegationD1. Only theaction_iddiffers. - An approval
P1is requested forA1. P1is granted, forA1only.
seq 1 DELEGATION_CREATED D1 human → agent, approval band 100–1,000 EUR
seq 2 ACTION_REQUESTED A1 500 EUR via D1
seq 3 ACTION_REQUESTED A2 500 EUR via D1 (same fingerprint as A1)
seq 4 APPROVAL_REQUESTED P1 for A1
seq 5 APPROVAL_GRANTED P1 for A1No execution exists yet, so P1 is not consumed.
What each view answers
The views below are described in What is AI delegation?. The first two rows are evaluated at sequence 5. The last row compares two alternative histories: in one, A1 is executed at sequence 6 citing P1; in the other, A2 is executed at sequence 6 citing P1.
| Question asked | View | A1 | A2 | Reason |
|---|---|---|---|---|
| Would some chain held by the agent authorize this capability with these parameters now? | AVAILABLEauthorityAt, currentAuthority | AUTHORIZED, citing P1 | AUTHORIZED, citing P1 | The question is about a fingerprint, not an instance. A valid, unconsumed approval with the same fingerprint is sufficient by specification. |
| Would the delegation named by this action authorize this action instance now? | INVOKEDinvokedAuthorityNow | AUTHORIZED, citing P1 | REQUIRES_APPROVAL, via D1 | P1 is bound to the action_id of A1. Matching fingerprint, requester and delegation is not enough (I6). |
| Does the chain recorded by the execution validate this action at its decision point? | RECORDEDexecution.recordedValidation | AUTHORIZED, P1 consumed | REQUIRES_APPROVAL, P1 cited but not consumed | Citing P1 in the recorded chain does not bind it to A2; consumption also requires the same action_id. |
So it would be wrong to summarize the result as “Authority refuses A2”. In the INVOKED and RECORDED views, A2 is REQUIRES_APPROVAL: it still needs its own approval. In the AVAILABLE view, A2’s fingerprint is AUTHORIZED, because that view asks a different question.
Why the fingerprint view still answers yes
authorityAt is prospective. It never reads a previous ACTION_REQUESTED, so it has no instance to bind to: by specification, a valid and unconsumed approval whose fingerprint matches the requested capability and parameters is sufficient to produce AUTHORIZED. The same fingerprint-scoped question is answered by currentAuthority and execution.authorityAtDecision in explainAction.
That answer is useful for one question, “is there authority available for this kind of action?”, and misleading if read as “this action was approved”. Since the fix, the authority explain command prints a note whenever the fingerprint-scoped line is AUTHORIZED while the invoked delegation line is not, so the first is never read as proof about the second.
What changed
The requirement existed before the fix
Invariant I6 in SPEC.md already required the binding before the fix: an APPROVAL_GRANTED “covers exactly the action that requested it, identified by its action_id and by its exact action_fingerprint”. The implementation did not apply it completely. Pull request #9 closed that gap: before it, a grant matching the fingerprint, the requester and the invoked delegation could authorize a different, fingerprint-identical action instance in the action-specific resolutions.
The fix
Commit 46e1321 passes the queried action_id to the approval search used by the three resolutions that concern one specific action: the invoked delegation validation, the recorded chain validation, and the grant selection used by capability issuance. A grant bound to another action_id is skipped. The fingerprint-scoped resolution deliberately does not receive an action_id.
The scenario and its documentation came afterwards
The detailed scenario and its explanation were added or reinforced after the fix: the “I6 — action-instance approval binding” section of SPEC.md does not exist in the specification before the fix (it appears later, in commit 95cabde), whereas the I6 requirement itself does. The links in this article point to merge commit 8e37af7, which carries the current, cleaned-up version of the code, tests and documentation.
Evidence
- Post-audit regression test in
tests/adversarial/independent-audit.test.ts:A1isAUTHORIZEDwithP1andA2isREQUIRES_APPROVALviaD1. It was added after the audit, in46e1321, and is labeled as such in the file. - CLI test in
tests/cli/invoked-authority-rendering.test.ts: whenA2is executed citingP1, both its invoked and recorded lines readREQUIRES_APPROVAL, the output carries the fingerprint-scope note and never claims thatP1was consumed; whenA1is executed,P1is reported as consumed. - One test in
tests/adversarial/unit-binding.test.ts, “A2/A3: a newaction_id, in EUR or USD, gets nothing from P1”. It was added later, with pull request #10 on currency binding, not with pull request #9.
Mutation check
Removing the action_id comparison from the approval search in src/engine/evaluateConstraints.ts makes exactly the three tests above fail; the other 291 executed tests still pass. This was reproduced in a temporary copy of the repository at 8e37af7, without changing the repository itself.
Limits of this result
- Authority is a V0 project.
- Every event is
ASSERTED_UNVERIFIED. V0 provides no signatures and no identity verification: the result says nothing about whether the events were really issued by the principals they name. - The result concerns the deterministic rules of the engine, evaluated on the event history it receives.
- We know of no production system in which this gap was exploited.
- No performance or load-testing claim is made.
- I6 already required instance binding; pull request #9 corrected an implementation gap, not the specification.
- The fingerprint-scoped view intentionally keeps a different meaning, described above.
Open questions
- Is a fingerprint-scoped available view useful to expose, or is it dangerous because it can be misread as an approval of a specific action?
- Which properties, in addition to the action instance, should an approval bind to?
- Have you met a similar approval replay in a real system?
- Is there an event sequence in which
A2can still obtain the benefit ofA1’s approval?
Counterexamples that do not describe an unfixed exploitable flaw can be discussed in public on the repository. Anything exploitable should be reported privately: see the security page.