← All write-ups

90-day API token rotation with a token that can do nothing else

Details are generalized: no hostnames, addresses, identifiers or credentials.

How this was built: I design, direct and review the work, and build it hands-on with Claude, an AI coding assistant. The decisions, and the homelab, are mine.

A working API token is used by scripts, automation and monitoring, so it gets copied into a lot of places. If that token can also create tokens, any one of those copies is a full account takeover. This is the two-token design I use to rotate every 90 days without giving the working token that power.

The problem

Cloudflare fronts my homelab: DNS, tunnels, Zero Trust Access and WAF rules. Several consumers need an API token to manage them: maintenance scripts, the n8n automation platform, and the agent that renews and checks TLS certificates.

Long-lived tokens are a liability, so they should rotate. But to rotate itself, a token needs permission to edit API tokens. Whoever holds that permission can mint a new token with any scope. Granting it to the token that every script reads makes the least-protected copy the most powerful credential in the account.

The design: two tokens

TokenPermissionsWho can read it
MainThe working scopes (DNS, tunnels, Access, transform rules). No token permissions.Scripts, automation, monitoring
RotatorOnly "API Tokens: Edit", restricted to the homelab's IP addressThe rotation script, nothing else

A leaked main token can change DNS but cannot create credentials. The rotator can create credentials, but it's never used for anything else, and it only works from home.

The rotation flow

  1. Check. A daily scheduled job runs the rotation script in "rotate if due" mode. Most days it does nothing.
  2. Roll the main token. The rotator calls the API's roll operation, which changes the token's secret value and leaves its permissions alone.
  3. Save safely. The raw API response is written to an owner-only file before it's parsed, so a parsing bug can't lose the only copy of a new secret. The token file itself is replaced atomically.
  4. Update every consumer. The new value goes to the credential file and into the automation platform's encrypted credential store.
  5. Prove it. A real call through the automation platform must succeed with the new value, and the old value must be rejected. The recorded rotation date is written only after both checks pass.
  6. Rotate the rotator. Once the main token is proven, the rotator rolls its own value the same way.
  7. Report on change only. Discord gets a green message on rotation and a red one on failure, with repair steps. If the script's output can't be parsed at all, the alert says "could not run" rather than staying silent.

What was harder than expected

The rotator can't be created by API

A token that can edit tokens still can't use the API to create another token with that same power. The API refuses sub-tokens that could manage tokens. So creating the rotator was a one-time manual step in the dashboard. After that, the rotator can roll both tokens by API indefinitely.

Taking power away needs the other token

Removing the token permission from the main token, and later adding a narrowly scoped permission it needed, can't be done with the main token itself. The rotator does it with a full-definition update: every other scope and the IP restriction are sent back unchanged, and only the one permission differs. The assistant first reported that this couldn't be automated, because it had only tested the main token. The rotator could grant the permission all along. It's worth checking every credential you hold before concluding something can't be done.

Not every token can rotate itself

Some providers offer no API to regenerate a token. GitHub's fine-grained personal access tokens are one example. Those get the narrowest scope that works, a fixed expiry date, and a reminder well before it, which is the honest fallback when automation isn't possible.

Result

Both tokens rotate every 90 days with nobody touching them, every rotation is proven end to end before it's recorded, and the credential that's copied everywhere can't be used to create new credentials. The general principle: separate the power to do the work from the power to issue credentials, and never let the widely-shared credential hold the second one.