DEV Community

Cover image for Who revoked that token? Reading the dotvault audit log
uehara
uehara

Posted on

Who revoked that token? Reading the dotvault audit log

This is part 10 of the series "dotvault, Start to Finish: One Command from Your Laptop to CI, Your Team, and Your AI Agents." dotvault will be released free of charge when this series ends.

The demo org had two service tokens called ci-deploy. When a CI pull started failing with AUTH_REQUIRED, the name could not say which of the two had been revoked. The dotvault audit log could: it named the token by its ID, the person who revoked it, the time, and even the environment the token had been created for.

The error line itself reads "invalid, expired or revoked service token". It does not say which of the three, and it does not say who. This part follows that question through the audit log, the console page that records service tokens being created and revoked and projects being archived and deleted, one row per action, each with an actor and a target. Every screen comes from a Personal org on the free plan.

This is part 10 of 12.

The short version

  • When CI fails with AUTH_REQUIRED, start with dotvault whoami. The eight characters after est_ are the start of the token's Token ID, which is not a secret.
  • Filter the audit log by the action service_token.revoked. Only revocations remain, each with the actor's email, the time and the token's name.
  • Paste the full Token ID into Target ID. You get that one token from creation to revocation, and the creation row records the environment it was made for and its expiry.
  • Token names can repeat. The Token ID is what tells tokens apart.
  • Every filter lives in the URL. Save the link and the same view opens again, with the same date, actor, action and target.

Before you read on

This picks up from part 7, where we created a service token and pulled with it from CI. You can follow along without it.

1. What the failing job tells you

The top half of the next screen is a pull right after the token was created, under CI conditions. The bottom half is the same pull after the token was revoked in the console.

Terminal screen (recomposed). Under "Before", dotvault --version prints 0.16.0, dotvault whoami prints "Service token (DOTVAULT_TOKEN=est_1ad24f14…) — CI / Lambda mode" and dotvault pull demo-app --env production succeeds with "Wrote 5 keys" and "1 file synced". Under "After", the same pull stops with "✖ AUTH_REQUIRED: invalid, expired or revoked service token" and echo $? prints 3. The following dotvault list --project demo-app stops with the same AUTH_REQUIRED, and echo $? prints 3.

After the revocation, pull exits with code 3 (AUTH_REQUIRED), and the message only narrows it down to three possibilities.

The useful part is est_1ad24f14… in the whoami line. The eight characters after est_ are the beginning of the Token ID. The console says as much when it shows a new token: the Token ID is not a secret, and it is the part of the token between est_ and the .. Note those eight characters and switch to the console.

whoami does not contact the server, so a revoked token prints the same line. Its output goes on with a note, left out of the figure above, that points to the real check: dotvault list --project <name>. Exit code 3 (AUTH_REQUIRED) means the token is revoked, expired or invalid.

2. Find the revocation

Open Audit Log in the console sidebar. Each row is one action, in five columns: Timestamp, Actor, Action, Target and Metadata.

Pick service_token.revoked in the Action list and press Apply. Only revocations remain.

Audit log (English UI), filter bar and list. Action is set to service_token.revoked and three rows are shown. The top row: 11/2/2026, 5:39:38 PM, actor dev-a (demo email) via web, target service_token/1ad24f14e0c281bc75dd253374de3ac3, metadata already_revoked:false and name:ci-deploy. The other two rows revoke the claude-agent-admin and claude-agent tokens.

The top row is the one that broke the CI job:

Column Value Meaning
Timestamp 5:39:38 PM when it was revoked
Actor dev-a's email · web who revoked it; web means the console
Target service_token/1ad24f14… the Token ID
Metadata name:ci-deploy the token's name
Metadata already_revoked:false it was still active until then

The Token ID starts with 1ad24f14, matching whoami. So this is the revocation, and the demo user dev-a did it. The other two rows are the AI agent tokens from part 8, revoked at the end of that part.

3. One token, from creation to revocation

To see the rest of that token's story, set Action back to "— any —", paste the full Token ID into Target ID and press Apply. You can copy the ID from the Target column or with the Copy button in the Service Tokens list.

Audit log (English UI). Target ID holds 1ad24f14e0c281bc75dd253374de3ac3 and two rows are shown: service_token.revoked at 5:39:38 PM (already_revoked:false, name:ci-deploy) and service_token.created at 5:38:09 PM (envs:production, expires_at:1796200689, name:ci-deploy). Both actors are the demo user dev-a's email via web.

Two rows, nothing else. service_token.created at the bottom is the creation, service_token.revoked above it the revocation. The creation row keeps what the token was made with:

  • envs:production: the token was limited to the production environment.
  • expires_at:1796200689: its expiry in Unix time (seconds since 1970), exactly 30 days after creation.
  • name:ci-deploy: the name it was given.

Who created it, when, for which environment, and who revoked it and when: two rows answer all of it.

Target ID matches the whole ID only. A prefix such as 1ad24f14 returns nothing.

4. Same name, different ID

Set the Service Tokens list to "Revoked only".

Service Tokens list (English UI), filtered to Revoked only, five rows: ci-nightly; ci-deploy (1ad24f14e0c281bc75dd253374de3ac3, envs production, last used 11/2/2026, 5:39:10 PM, last-use IP masked); a second ci-deploy (1f121d8e8c10adc7fab44f81a9a587f5); claude-agent; claude-agent-admin. Every status is revoked.

There are two ci-deploy tokens: one from part 7 and the one created for this part. dotvault lets two tokens share a name, so the name cannot say which one was revoked. The Token ID can. The eight characters from whoami, the Target in the audit log and the Token ID in this list are the same ID.

"Last used (hourly)" is when the token was last used. This ci-deploy was used at 5:39:10 PM and revoked 28 seconds later, at 5:39:38 PM. The column is sampled once an hour per token, so it is not always the latest use, as the note under the list says.

Names that include the pipeline and the environment keep the list easier to search.

5. Tokens created and revoked, projects archived and deleted since a given day

To see tokens created and revoked and projects archived and deleted since a given day, use Since and Until. Put a date in Since and press Apply, and the rows for those four actions since that day are listed.

Audit log (English UI). Since is set to 2026/11/02 and four rows are shown: project.archived (5:46:25 PM) and project.deleted (5:47:27 PM, metadata secrets_deleted:0) for project/demo-scratch, then service_token.revoked and service_token.created for service_token/1ad24f14…. The project rows show the actor as the ID f4785468-4031-7039-7e91-a00634e86a4f via web; the token rows show dev-a's email via web.

Next to the two token rows are two project rows. demo-scratch was created for this part, archived (project.archived) and deleted (project.deleted). secrets_deleted:0 on the delete row is the number of secrets the project held when it was deleted. Part 3 covered archiving and deleting.

The project rows show the actor as an ID, f4785468-…, rather than an email. That ID is dev-a's user ID, so all four rows are the same person. As the column header "Actor (email / sub)" suggests, the actor filter takes either:

Actor filter Rows returned
dev-a's email the two token rows
f4785468 (start of the ID) all four rows

Filtering by email drops rows that show an ID. To catch every audit log row for one person, filter by the ID. Unlike Target ID, the actor filter also matches part of a value.

6. Filters live in the URL

The banner on the audit page says filter settings are kept in the URL so a view can be shared. The filters in this part became these URLs:

https://app.dotvault.io/admin/audit?action=service_token.revoked
https://app.dotvault.io/admin/audit?target=1ad24f14e0c281bc75dd253374de3ac3
https://app.dotvault.io/admin/audit?since=2026-11-02
https://app.dotvault.io/admin/audit?actor=f4785468&since=2026-11-02
Enter fullscreen mode Exit fullscreen mode

Join them with & to combine filters: ?action=service_token.revoked&since=… lists only the tokens revoked since that day. Paste the URL into your notes and the same view is one click away. The "Audit log" link at the top of the Service Tokens page also leads here.

Wrapping up

When a CI pull stops, read the Token ID prefix from dotvault whoami, filter the audit log by service_token.revoked, then by that Token ID. The rows show who revoked the token and when, and who created it, when, and for which environment. Creating and revoking a token, archiving and deleting a project: each leaves a row with the actor and the target, and on the free plan too, you can trace them after the fact.


dotvault stores the secrets in your .env and config files server-side and hands them to any authorised developer, CI job or AI agent with a single command (https://dotvault.io). It will be released free of charge when this series ends, and the steps in these articles are the steps you will use on day one.

If you find this useful, please like and follow.

Our products are listed at https://www.eln.ne.jp/products


EarthLink Network builds its own products to run the whole company on AI — each one born from a need along the way. See the full index of what we build:

→ The 18 products EarthLink Network builds in-house

For more about the company and each product, visit www.eln.ne.jp.

Top comments (0)