Skip to content

bug: RFC 7523 actor assertions are replayable within their TTL (jti not consumed) #282

Description

@KunalJavelin

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

  1. 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.
  2. 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.
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions