Skip to content

Revoke a session or a grant

A grant ends on its own when it expires. To end access sooner, revoke the grant, or end the session that holds it: ending a session revokes every grant it held and cancels every request it still had pending. Ending a grant stops the session reading its values again; a command already running with a value keeps it.

Revoking a grant in Dispatch ends it at once. A grant someone approved is asked for again from its approver at the session’s next request. When you revoke a grant your own session got without asking, you withhold its secrets from that session: the session’s other grants that got them without asking end with it, and its next request for them is sent to their owner for approval (to anyone signed in, for a shared secret) instead of being granted at once, so the session asks before it gets them again; your other sessions still get them at once. The withhold is the session’s, not the grant’s, so it holds even when the grant had already ended by the time you clicked Revoke (the session revoked it itself, or it expired, after the page loaded). Only the session’s own person withholds: a person who revokes a grant they approved on someone else’s session ends that grant alone. To end every session’s access to a secret, change its tags. To end every session a machine started, and stop it starting more, revoke its machine login.

Dispatch’s Settings page lists your Live grants: every live grant of a session you operate, whether it was granted automatically or approved by someone, and every grant you approved on anyone’s session. Each row names its enrollment, its secrets, how it was Granted (Automatically, or Approved by an email), and when it was created and expires. Click Revoke to end one at once; the session’s next read of it is refused GRANT_NOT_LIVE. The broker allows it when you are the grant’s approver or its session’s operator, and refuses anyone else NOT_APPROVER.

Terminal window
$ curl -s -X POST -H "Authorization: Bearer $AGENT_SECRETS_UI_TOKEN" -d '{"approver":"ada@example.com"}' "$AGENT_SECRETS_URL/v1/grants/<grant id>/revoke-by-approver"
{"state":"revoked"}

(That is the call Dispatch’s server makes with the broker’s UI token, the credential only Dispatch’s server holds; on the local stack you can make it yourself.)

From your own shell on a machine that is logged in (not from inside an agent session), agent-secrets grant list lists the same grants as Live grants, and agent-secrets grant revoke ends one as Revoke does, withholding included. Both call the broker under the machine’s login: the helper signs each call with the machine’s credential, and refuses a process inside a registered session’s process tree (IN_SESSION), so an agent’s own commands cannot act as you. The check covers that process tree only. A process a session sends out of it, with ( cmd & ), setsid -f or a tmux server the session started, passes it, and any process running as your user can stop the helper anyway. Through these commands such a process can list and revoke your own machine logins, never a service’s machine login, and your grants, which include grants you approved on any session, a service’s pod among them. agent-secrets enroll --helper is open to any process of your user, so such a process can also enroll a box and read your agent secrets.

Terminal window
$ agent-secrets grant list
GRANT_ID SECRETS GRANTED APPROVER SESSION OPERATOR EXPIRES
f26b6b78-2819-4b1d-abbd-d6990ca1a776 DEMO_API_KEY approval ada@example.com session/example-host-laptop:1989345:35362384 ada@example.com 2026-10-10T15:56:07Z
cc63d479-ddae-4052-b93b-b4b5df37adf0 DEMO_READ_TOKEN automatic - session/example-host-devbox:1989344:35362384 ada@example.com session-end
$ agent-secrets grant revoke f26b6b78-2819-4b1d-abbd-d6990ca1a776
revoked f26b6b78-2819-4b1d-abbd-d6990ca1a776

GRANTED is automatic or approval, and APPROVER is - for an automatic grant. EXPIRES is session-end for a grant that lasts until its session ends, and a time for one its request gave a --ttl. --json prints the broker’s answer, the same body the Live grants page reads, whose expires_at is null for a grant that lasts until its session ends.

A session can end any of its own grants, by the grant id agent-secrets self lists. A session ending its own grant withholds nothing: its next request is decided as before.

Terminal window
$ agent-secrets revoke b51e58bc-3b7b-4144-a2ed-5821f232d612
revoked
$ agent-secrets self
enrollment_id: 2b8627f4-9a2a-4c12-85bf-910144bc0b5c
kind: session
operator: ada@example.com
lease_expires_at: 2026-10-03T03:36:40Z

Retag the secret so its sessions no longer get it automatically: set its tier to human (agent-secrets secret retag NAME --tier human), so every request for it needs a person’s approval, or give it another owner, so the sessions that had it are no longer the owner’s own. Deleting the secret (agent-secrets secret delete NAME), or moving it out of BROKER_SECRETS_PREFIX, ends access altogether (manage a secret). Once the broker rereads the secret, at once after a write through agent-secrets secret and, after one made any other way, within about ten minutes on AWS Secrets Manager (on Google Secret Manager usually as soon, but Google bounds its listing’s lag only at a few hours, so ask for a reread after a gcloud write), it refuses each read of a grant given automatically under the old tags with GRANT_NOT_LIVE, and the session’s next request for the secret waits for a person’s approval. The change applies to every session that got the secret automatically, where revoking a grant of your own session in Dispatch withholds it from that session alone.

A session that no longer needs what it asked for withdraws the request, which takes it off the approver’s list. It names the request by the request id agent-secrets printed when it asked (agent-secrets request prints it, and so does a command whose --wait ran out):

Terminal window
$ agent-secrets cancel 715ea84a-6f5c-4230-83c9-1cf258acd88b
cancelled
$ agent-secrets status 715ea84a-6f5c-4230-83c9-1cf258acd88b
state: cancelled
decided_by: session:2b8627f4-9a2a-4c12-85bf-910144bc0b5c

Ending a session’s enrollment revokes all of its grants and cancels all of its pending requests in one step, and the approver’s Inbox drops them. A session ends in one of three ways:

  • Its launcher revokes it. A machine session ends when its process exits: the helper sees the registered process go and revokes its enrollment (it logs session retired … why="process exited"). agent-secrets-helper sessions lists the live ones. A box’s launcher revokes it with agent-secrets unenroll --helper --enrollment <id>.
  • Its lease lapses. A session that stops renewing (a pod that is gone, a box whose agent-secrets renew stopped, or a machine that went away) is refused ENROLLMENT_NOT_LIVE on its next renewal from the moment its lease lapses, BROKER_LEASE_SECONDS after its last renewal. Other session calls are refused PROOF_INVALID. The broker’s sweep ends it on its next run, within BROKER_SWEEP_SECONDS, and logs broker sweeper: ended an enrollment whose lease lapsed. The configuration reference gives both settings’ defaults.
  • Its machine login is revoked. Whoever may revoke the machine login that enrolled it — the person who approved a person’s machine login, or anyone signed in for a service’s — revokes that login, which ends every session it enrolled (end a machine’s login).

A person’s machine login lasts BROKER_LAUNCHER_CREDENTIAL_SECONDS from its approval, but its sessions outlive it: a session renews its lease with its own key, never with the machine’s credential, so a box keeps working after the machine’s credential expires, and so does a machine session while the helper that renews it runs. A login can be ended, with every session it enrolled, at any time: for a machine that is lost, compromised or no longer used, or a Legion daemon whose pods must lose their secrets now. Dispatch’s machine-login page (/credentials/machine) lists Machine logins: every machine logged in as you, and every service’s login, such as the Legion daemon’s, shown as legion-daemon on <host>, each with its Credential id, who approved it (No one: and the ServiceAccount that logged in, for a daemon’s token login), when its login was issued and when it expires. The host is the name its machine reported, so two logins can share it; the credential id is the one every enrollment the login made names, and it tells them apart. A login that has expired stays listed, marked expired, sessions still running, while a session it enrolled still runs, so revoking every row a machine has ends every session it started, those of its earlier logins included. Click Revoke on its row and confirm. The broker then, at once:

  • refuses the login’s credential, so it enrolls no more sessions; and
  • ends every session it enrolled (machine sessions and boxes, or every worker pod of the Legion daemon’s login) as ending a session does: each one’s grants are revoked and its pending requests cancelled, and its next call is refused PROOF_INVALID.

Only the person who approved a person’s machine login may revoke it; the broker refuses anyone else NOT_APPROVER. Anyone signed in may revoke a service’s login. A revoked login stays revoked: the machine runs agent-secrets machine login again and its person approves the new code, while the Legion daemon logs in again with its own Kubernetes token, which no person approves, and enrolls the pods it launches from then on. A worker pod already running when its login was revoked stays without secrets until the daemon replaces it. To keep a daemon from logging in again, remove its account from the broker’s BROKER_SERVICE_LOGINS and redeploy the broker (Operate).

From your own shell on a logged-in machine, agent-secrets machine list lists your own machines’ logins, row for row as the machine-login page lists them, and agent-secrets machine revoke ends one, under that machine’s login. A service’s login is not listed there, and machine revoke of one is refused NOT_FOUND as for an id it does not know. Revoking the machine’s own login (its id is the one machine revoke warns about) ends that machine’s access too: its next command is refused LAUNCHER_INVALID until it logs in again.

Terminal window
$ agent-secrets machine list
CREDENTIAL_ID HOST APPROVED_BY ISSUED EXPIRES STATE
d4187389-953c-4156-90c1-1dd937e9bf0b example-host-devbox ada@example.com 2026-10-10T14:56:01Z 2026-10-17T14:56:01Z ok
a2df6d16-adc1-434e-b479-496defb3a5c8 example-host-laptop ada@example.com 2026-10-10T14:55:53Z 2026-10-17T14:55:53Z ok
$ agent-secrets machine revoke 8f4c8ae4-b73e-4a60-a591-725667480f1a
agent-secrets machine revoke: no such machine login (NOT_FOUND)
$ agent-secrets machine revoke a2df6d16-adc1-434e-b479-496defb3a5c8
revoked a2df6d16-adc1-434e-b479-496defb3a5c8

The machine-login page lists the services’ logins, the Legion daemon’s, beside them, and revokes them. This one a person approved before services logged in with their own Kubernetes token; a daemon’s token login is listed the same way with approved_by empty and subject naming its ServiceAccount:

Terminal window
$ curl -s -H "Authorization: Bearer $AGENT_SECRETS_UI_TOKEN" "$AGENT_SECRETS_URL/v1/launcher-credentials?approver=ada@example.com"
{"credentials":[{"credential_id":"d4187389-953c-4156-90c1-1dd937e9bf0b","host":"example-host-devbox","service":null,"subject":null,"approved_by":"ada@example.com","issued_at":"2026-10-10T14:56:01.121955Z","expires_at":"2026-10-17T14:56:01.122701Z","expired":false},{"credential_id":"8f4c8ae4-b73e-4a60-a591-725667480f1a","host":"example-host-cluster","service":"legion-daemon","subject":null,"approved_by":"bob@example.com","issued_at":"2026-10-10T14:55:59.906427Z","expires_at":"2026-10-17T14:55:59.9071Z","expired":false}]}
$ curl -s -X POST -H "Authorization: Bearer $AGENT_SECRETS_UI_TOKEN" -d '{"approver":"ada@example.com"}' "$AGENT_SECRETS_URL/v1/launcher-credentials/8f4c8ae4-b73e-4a60-a591-725667480f1a/revoke-by-approver"
{"state":"revoked"}

(Those are the calls Dispatch’s server makes, as above.)

STATE is expired for a login past its expiry whose sessions still run. --json prints the broker’s answer, the machine-login page’s rows for your own machines. On the machine whose login was revoked, every machine and grant command is refused from then on:

Terminal window
$ agent-secrets machine list
agent-secrets machine list: the launcher credential is not valid (LAUNCHER_INVALID); this machine's login is expired or revoked (run: agent-secrets machine login)

Nothing tells the machine’s helper; it finds out the next time it calls the broker. A session’s renewal, within a third of BROKER_LEASE_SECONDS, is refused; revoking that session’s enrollment is refused too, and the helper drops the credential and logs at ERROR the broker refused the launcher credential (…); cleared: no session can enroll until a human approves a new machine login. From then agent-secrets machine login-status exits 1:

Terminal window
$ agent-secrets machine login-status
expired
agent-secrets machine login-status: the broker refused the launcher credential (expired or revoked, or a proof it could not verify, such as clock skew or an AGENT_SECRETS_URL mismatch); run: agent-secrets machine login

A helper with no session running finds out when it next enrolls one. The Legion daemon finds out when it next enrolls or ends a pod: the broker refuses its credential LAUNCHER_INVALID, and the daemon logs in again with its Kubernetes token, under a new credential, unless its account is no longer in the broker’s BROKER_SERVICE_LOGINS. The pods it had enrolled do not wait for that: their sessions ended with the revoke, so their agents’ agent-secrets calls are refused PROOF_INVALID from then on. The daemon does not enroll a running pod again: each stays without secrets until the daemon replaces it, and only the pods launched after its new login are enrolled.

Stopping the helper also stops the machine from enrolling sessions, since its credential lives only in the helper’s memory, but it ends none. A machine session lapses within BROKER_LEASE_SECONDS once the helper stops, since the helper is what renews it. A box renews itself (agent-secrets renew), so it keeps working after the helper stops and after the machine’s credential expires. Revoking the machine login ends both, and its row stays on the machine-login page while either runs, marked expired, sessions still running once the login has expired. Without Dispatch, end each box with agent-secrets unenroll --helper --enrollment <id>, run against a helper logged in as the box’s operator, or stop its agent-secrets renew, after which it lapses within BROKER_LEASE_SECONDS.