deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

Leaked AI agent credentials stayed valid for a day; a 120-second expiry closes the gap

A VentureBeat report cited by a dev.to post says exposed AI agent credentials were used to breach 395 organizations; a side-by-side test shows two-minute token lifetimes render stolen agent keys useless.

Leaked AI agent credentials stayed valid for a day; a 120-second expiry closes the gap

Agent credentials breached 395 organizations

A September 2026 VentureBeat report, cited in a dev.to engineering post, found that attackers used exposed AI agent credentials to break into 395 organizations. The post's central observation is that identity stacks have been provisioning agent credentials with the same assumptions as human passwords — issue them once and let them live for a long time — and attackers have noticed. A separate incident at Hugging Face, also referenced in the post, reportedly came down to the same failure: a credential that stayed live long after the task that requested it had finished.

How agent tokens outlive their tasks

Most agents authenticate through a machine-to-machine OAuth flow. At startup the agent trades a client ID and secret for a signed access token, then reuses that token on every API call until the process restarts. If the token leaks into a log line, a support ticket or a copied environment block, it keeps working for whatever lifetime the issuing provider configured. The author notes that for most identity providers that window runs from roughly an hour to a full day — a long time for a stolen credential to stay useful, especially when agent processes run for hours or days.

Two agents, one identity provider

To size the gap, the author built two variants of the same agent against Kinde, an identity provider that issues M2M access tokens, verifies every call against them, and lets each application set its own token lifetime. The first variant fetches a token once and never checks it again. The second treats the token as perishable: it checks the token's age before every call and fetches a replacement just before expiry, with a 15-second safety margin.

Both variants use the standard client-credentials grant — no human and no browser involved — and the grant itself does not decide how long a token lives; the application's settings do. In this build the static agent kept Kinde's default 86,400-second (24-hour) expiry, while the rotating agent's application was set to 120 seconds.

Enforcement sits at the API

Both agents call the same API, a single Convex function that runs one check on every request with no branch for which agent is calling. It reads the bearer token, verifies the signature against Kinde's public key set (JWKS), and rejects anything whose exp claim has passed, using the jwtVerify call from the jose library. That single check is what turns a configured expiry into an actual rejection.

A companion action registry limits both agents to three calls — list_records, read_record and export_records — and refuses anything else before token validation, keeping the test focused on token lifetime rather than on what agents are allowed to do.

Replaying a stolen token

The live proof runs in four steps. Each agent requests a token; both tokens are captured at the same instant, the way a harvested log or a pasted support ticket might expose them; the script waits past the shorter token's expiry; and each captured token is replayed directly against the API with no agent involved.

As the post's sequence diagram lays out, the replayed static token still returns a 200 with data, while the replayed rotating token draws a 401 because it has already expired. The only difference between the two setups is one number in Kinde's dashboard — the identical verification code enforces both schedules without any code change.

A logging bug caught during hardening

Reviewing the API turned up a defect: requests that carried no token, or a token from an unrecognized application, defaulted in the logs to the static agent, making anonymous or malformed traffic look as though that agent had made it. The bug did not affect the proof, since every call in the build came from one of the two known agents, and it was fixed before the run — unattributed requests now log as a separate unknown value.

Why it matters

The 395-organization figure points at a lifecycle problem rather than a protocol problem. OAuth's client-credentials grant is working as designed; what is broken is that agents hold tokens sized for human-era workflows, and identity systems rarely expire them on a schedule that matches how agents actually run. The experiment shows the remediation is inexpensive: shorten the token lifetime per application, rotate before expiry inside the agent's credential manager, and verify signature and expiry at the API boundary. Teams running long-lived agents should assume any token can leak and ask how much damage a copy can do minutes later — a question a two-minute expiry answers far better than a 24-hour one.

  • #ai-agents
  • #security
  • #oauth
  • #identity
  • #access-tokens

Related posts