Skip to content

Build1 publisher3 min readPublished

Shrinking a Kinde agent token to 120 seconds puts revocation in the exp claim

A dev.to build runs two agents against one Convex API, one holding Kinde's default token and one rebuilding its token every two minutes. The client secret in the environment variable keeps the lifetime it always had.

The Engineer · Build desk

Illustration accompanying Shrinking a Kinde agent token to 120 seconds puts revocation in the exp claim

What happened

  • Most agents take one machine-to-machine token at start-up and hold it for the life of the process, so a token copied out of a log or an environment variable stays valid as long as the issuer configured it.
  • The build gives one agent a 24-hour token and the other a 120-second token, both against Kinde.
  • Both agents call the same API, which runs the same check on every request with no branch for which agent is calling.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint With verification done offline against Kinde's key set, an incident response has nothing to call: the only way to shorten a stolen token's remaining life is to have configured it short before the leak.
  • cost A short lifetime bills the token endpoint instead of the API, because every refresh is a round trip the agent waits on and the issuer's rate limits become the agent's ceiling.
  • exposure Anyone who can read the agent's environment still holds a credential that mints fresh tokens on demand, and a shorter access-token lifetime does not touch it.
  • decision Whoever owns the Kinde application settings decides the agent's exposure window, so the argument about lifetimes happens in an admin console rather than in the agent's repository.

The only thing enforcing an expiry here is one function call. The API is a single Convex function: it reads the bearer token, verifies the signature against Kinde's published key set, and checks the token has not expired, and jose's `jwtVerify` throws the moment the signature is wrong or the `exp` claim has passed [6][7]. It also refuses a token whose `azp` is not a client ID it recognises, returning a 403 with "token not issued to a known agent client" [8]. Kinde signs the token, and any server that trusts Kinde can verify that signature without calling Kinde back [9]. So there is no point in that path where the API can ask whether a token has been withdrawn.

Which is why the lifetime number is the whole control. In Kinde it is set per application, and the client-credentials grant hands back the same kind of token whether it lives two minutes or two days [5]. The agent posts its client ID and secret straight to the token endpoint, with no human and no browser in the flow [10].

At a 120-second lifetime, an agent running for a full day needs roughly 720 grants instead of the one a 24-hour token needs: 86,400 seconds divided by 120 [11]. The rotating agent gets there by checking the token's age before every call and throwing the token away before it expires [12].

Rotation stops at the client ID and secret. According to the write-up, most teams set them once in an environment variable and leave them alone [13], and those two values are what mint access tokens in the first place [14]. A 120-second token caps what a copied token is worth at about two minutes. A copied client secret works on the same terms it always did, so the two exposures need separate handling.

The post is a build log with the attack still ahead of it. "Then I'm going to try to steal both tokens and reuse them again," the author wrote [15]. Its sequence diagram states the expected result: replay the static token and the API answers 200, replay the rotating one and it answers 401 [16].

The framing rests on a VentureBeat report the post cites, which said AI agents had used exposed credentials to breach 395 organizations in September 2026 [1], plus the post's argument that identity systems still treat an agent's credential the way they treat a human's password [2]. For that count to say anything about your agents, the exposed credentials would have to have been long-lived access tokens, not static secrets or session material. The post does not break the 395 down by credential type. The Hugging Face case it cites does match the pattern it describes: an agent held a credential that outlived the task it was issued for [3].

What to watch

  • Whether the post publishes the replay results, so the 401 comes from a captured token.
  • Whether identity providers move their default M2M access token lifetime below an hour.
  • A breakdown of VentureBeat's 395 organizations by credential type, which would show whether long-lived access tokens or static secrets were involved.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories