Use promo code BETATEST1 for full access
seekrit
← all posts

Three hours from one developer token to full cloud admin

Patterns

Some numbers from Anthropic's September 2026 threat intelligence report, all from the same financially motivated group:

  • A single developer token escalated to full administrative control of a cloud environment in about three hours.
  • 2,100+ Azure AD token sets pulled across more than forty corporate tenants in roughly thirty-four hours.
  • At a SaaS provider, initial access to bulk theft: two to three hours.

And then, from the same report, the number that doesn't match the others: one stolen API key was used for three weeks to keep attacking further organizations.

That pair is the whole problem. The attacker operates on a clock measured in hours. The credential operates on a clock measured in whenever somebody gets around to it.

The cadence you're defending with was set for a different attacker

Anthropic's own trend section puts it plainly: AI autonomy compresses the cost of an operation while leaving the payoff unchanged. The campaigns in this report run parallel subagents for reconnaissance, keep persistent campaign memory — target lists, harvested credentials, standing instructions — across sessions, and resume mid-campaign with accumulated context. One group maintained thirteen standing collection agents on scheduled jobs.

Nothing about that changes what a stolen credential is. It changes how long it takes to convert one into everything else.

Meanwhile the standard control is rotation, and rotation is a lottery ticket. If you rotate database passwords quarterly and an attacker converts a leaked credential into domain admin in three hours, your rotation schedule has approximately zero probability of being the thing that saves you. Rotating monthly instead of quarterly improves those odds by a factor that rounds to nothing.

The gap isn't closed by rotating faster. It's closed by not issuing the kind of credential that needs rotating.

note

A credential that expires in thirty minutes has no rotation policy, no offboarding checklist entry, and no "is this still in use?" conversation. It ages out faster than an incident can be opened.

Three separate dials

"Reduce the blast radius" is advice nobody can act on. There are three concrete properties, and they fail independently, so it's worth knowing which one you're actually turning.

1. Lifetime — how long a leaked value stays useful

The Vault-style answer is dynamic secrets: mint a credential per session, scoped and expiring. The usual catch is that the control plane generates and hands out the password, so your secrets manager sees the plaintext of every database login it issues — which puts you right back in the first half of this report, where compromising the thing that legitimately holds plaintext is the entire technique.

Temporary access inverts that. For Postgres: your machine generates a random password locally, computes the SCRAM-SHA-256 verifier locally, and sends seekrit only the verifier. We run CREATE ROLE … PASSWORD '<verifier>' VALID UNTIL …, and you connect with a password we never received. The verifier isn't sufficient to authenticate — SCRAM login needs ClientKey, and the verifier stores only its hash — so a dump of pg_authid, the query log, or a backup can't log in either.

psql "$(seekrit pg lease prod-db --ttl 30m)"

The same trick runs on MySQL/MariaDB (mysql_native_password hash), Redis (ACL SHA-256 digest), and SSH, where it's cleaner still: you generate an ephemeral keypair locally, send the public key, and get back a signed certificate. AWS STS, GCP, and MongoDB can't take a client-computed verifier, so those are wrapped to your public key instead — the control plane relays ciphertext, and the broker sees plaintext transiently. The docs grade every provider by which of those it gets.

The honest caveat, since every dynamic-secrets engine has it: something has to authenticate as a privileged role to run CREATE ROLE. That admin credential is a second, narrower trust tier. You choose where it lives — decrypted transiently inside the broker, or held by a small provisioner you run in your own network that seekrit signs commands to and never sees the credential for.

2. Reach — how far one credential travels

Notice that the three-hour number describes an escalation: one developer token became cloud admin. The lifetime dial does nothing about that. Reach is separate.

A seekrit service token is bound to one app environment, and its role, environment binding, and expiry are fixed at mint — a credential whose authority could be edited afterwards makes the audit trail unreadable. Admin capability is a different token, and a non-admin token cannot mint one: capability never escalates itself. For network reach, the egress proxy's allowlist bounds hosts and methods and paths, because api.github.com is one host and a hundred different authorities.

3. Residency — where the plaintext sits while it's in use

A credential with a thirty-minute TTL that spends those thirty minutes in a long-lived agent's environment is readable by everything else in that process for the full window.

seekrit run resolves and injects into a child process, so the plaintext lives as long as the command does. The proxy goes further and keeps it out of the workload entirely — the process holds a placeholder, and substitution happens at the network boundary.

The part about revocation you should know before you need it

Revoking a token takes effect on its next request: the revoke drops the cached record that authentication reads as part of the same operation. If a copy somewhere outlives that, it's re-checked against the database within a minute of continued use.

But revoking a grant is not the same as making the data unreachable. A token that already fetched a wrapped data encryption key has the DEK. For a hard cutoff — a leaked token, a departing teammate — you revoke and rotate the environment key. Rotation is what makes the copy the attacker already holds useless.

That's worth internalizing precisely because of the three-hour number. If your response plan is "revoke the token," you have handled the next request and not the data already decrypted.

What this doesn't fix

A short TTL still leaves a window. Thirty minutes of valid credential in an attacker's hands is thirty minutes, and these campaigns move inside that. TTL bounds the damage; it doesn't prevent it.

Most upstreams won't issue one at all. Stripe, OpenAI, and the large majority of SaaS APIs hand you a long-lived key and nothing else. There is no lease to take. For those, lifetime isn't an available dial and the answer has to be residency — keep the value out of the workload and substitute at egress.

Some databases opt out of the zero-knowledge path. A couple of managed providers intercept role creation and require a plaintext password, which is incompatible with sending only a verifier. Neon and PlanetScale are the two named in the docs; there the choice is a different database or a different pattern, not a worse version of this one.

And none of it stops the intrusion. The campaigns in this report start with device-code phishing, an XSS bug, a WordPress race condition, stolen VPN appliance credentials. What these three dials decide is what an attacker is holding at hour three.

Try it

# Register a read-only Postgres target (prints a one-time setup query to run as admin)
SEEKRIT_PG_ADMIN_URL=postgres://admin:…@db.example.com:5432/app \
  seekrit pg target add --name prod-db --host db.example.com --database app --access readonly

# Lease a 30-minute credential; the password is shown once and stored nowhere
psql "$(seekrit pg lease prod-db --ttl 30m)"

seekrit pg leases shows the ledger, seekrit pg revoke <leaseId> drops a role immediately, and Temporary access covers the other providers.