diff --git a/CLAUDE.md b/CLAUDE.md index 173366a..50291e6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -445,9 +445,20 @@ true across a demotion rather than only at issuance. Scopes are eight resources — `servers`, `keys`, `secrets`, `workflows`, `monitors`, `vulns`, `workloads`, `settings` — each split into `:read` and `:write`, with `:write` satisfying a `:read` requirement on the same resource so a caller does not have -to hold both. `settings:write` covers token management itself, which is why -creating and revoking tokens is owner|admin territory the same as the rest of -Settings. Expiry is optional per token; `settings.api_token_max_days` caps how +to hold both. Any signed-in member may mint and revoke their **own** tokens — +there is no `RequireRole` on `POST /tokens` or `DELETE /tokens/:id` — because +`roleRank` already bounds what a token can do to no more than its creator's +own role, so a member cannot use a token to reach past themselves. Owner and +admin additionally see and revoke every token in the instance — `all=true` on +`GET /tokens` is gated by `elevated()` in `api/tokens.go`, and +`RevokeAPIToken` in `services/tokens.go` checks the same owner/admin condition +before letting a revoke target somebody else's token — neither is a +`RequireRole` middleware. +The `settings:read`/`settings:write` entries in `routeScopes` govern a +**token-authenticated** caller reaching the token endpoints — `RequireScopes` +no-ops entirely for a cookie session — so they say nothing about which human +role may call these routes with a session; that is `roleRank` and `elevated()`, +not the scope map. Expiry is optional per token; `settings.api_token_max_days` caps how far out a new one may be set, and when that cap is set a token requested with no expiry is refused rather than silently capped — the policy governs issuance only and never reaches back to invalidate a token already issued.