Summary
An RFC 7523 actor assertion can be exchanged more than once. Replaying the same assertion within its TTL yields additional delegated credentials, so the assertion is bearer material rather than a single-use proof of possession.
Evidence
Against dev1, minting one assertion and exchanging it twice:
[PASS] a well-formed assertion is accepted
[FAIL] an assertion is single-use (replay rejected)
replay ACCEPTED -- assertions are reusable within their TTL (120s)
Both exchanges returned valid, distinct delegated tokens. The assertion carried a unique jti, a 120s exp, and correct iss/sub/aud — it is well-formed; it is simply not consumed.
For contrast, the other assertion negatives all behave correctly:
[PASS] an assertion signed with another agent's key is rejected
[400] invalid_grant: actor_token validation failed
[PASS] an expired assertion is rejected
[400] invalid_grant: actor_token validation failed
[PASS] an assertion for the wrong audience is rejected
[400] invalid_grant: actor_token validation failed
[PASS] an identity with no registered key cannot be an actor
[400] invalid_grant: no public key registered for actor identity <id>
Impact
Not a privilege-escalation path on its own: the assertion still only proves the sub-agent's identity, the scope intersection still bounds what can be granted, and TTL is short. But it does change what the assertion is. Anything that observes one in flight — a log, an error report, a proxy, a compromised orchestrator — can mint further credentials for that sub-agent until it expires, without holding the sub-agent's private key.
That weakens the "unique, non-shareable credential" story we make for agent identity, since the material that proves possession is itself replayable. It came up while validating a customer requirement about per-agent credentials.
Fix options
- Enforce
jti single-use (recommended). Record consumed assertion jtis for the assertion's remaining lifetime and reject a repeat with invalid_grant. Bounded storage: the window is the assertion TTL, and there is precedent in the credential store already keyed on jti.
- Cap the assertion TTL server-side. Independent of 1, refusing assertions with a long
exp limits the replay window regardless of what the client chooses. Clients currently pick their own.
- Document the property if it is intentional. If replay is deliberately allowed, say so explicitly in the delegation docs — right now a reader would reasonably assume RFC 7523 assertions are consumed on use, which is the common convention.
Recommend 1, with 2 as defence in depth.
Summary
An RFC 7523 actor assertion can be exchanged more than once. Replaying the same assertion within its TTL yields additional delegated credentials, so the assertion is bearer material rather than a single-use proof of possession.
Evidence
Against dev1, minting one assertion and exchanging it twice:
Both exchanges returned valid, distinct delegated tokens. The assertion carried a unique
jti, a 120sexp, and correctiss/sub/aud— it is well-formed; it is simply not consumed.For contrast, the other assertion negatives all behave correctly:
Impact
Not a privilege-escalation path on its own: the assertion still only proves the sub-agent's identity, the scope intersection still bounds what can be granted, and TTL is short. But it does change what the assertion is. Anything that observes one in flight — a log, an error report, a proxy, a compromised orchestrator — can mint further credentials for that sub-agent until it expires, without holding the sub-agent's private key.
That weakens the "unique, non-shareable credential" story we make for agent identity, since the material that proves possession is itself replayable. It came up while validating a customer requirement about per-agent credentials.
Fix options
jtisingle-use (recommended). Record consumed assertionjtis for the assertion's remaining lifetime and reject a repeat withinvalid_grant. Bounded storage: the window is the assertion TTL, and there is precedent in the credential store already keyed onjti.explimits the replay window regardless of what the client chooses. Clients currently pick their own.Recommend 1, with 2 as defence in depth.