Auth & Org Membership
Octopool has four auth surfaces: caller auth for relay traffic, admin auth for provisioning, the GitHub-CLI login exchange that mints caller tokens, and website sessions for /dashboard. All of them are pinned to a single allowed GitHub org (ALLOWED_GITHUB_ORG, openclaw).
Source: src/auth.ts, src/callers.ts, src/web-session.ts, src/provisioning.ts, src/router.ts.
#Caller auth
Relay and health requests send Authorization: Bearer <octopool_caller_token>.
- The token is hashed (SHA-256, base64url) and matched against
caller_tokens.token_hash. Raw tokens are never stored. Each caller can hold one rotating token per named client, capped at 16 active clients; a new name retires the least recently updated stale session. - The caller must be
activeand granted the requested pool (caller_pools). - The caller's
org_loginmust equalALLOWED_GITHUB_ORG, else403 org_denied. - Org membership is re-verified on use once it goes stale (
ORG_VERIFY_TTL_SECONDS, default 24h), using the org verifier token. A member who leaves the org loses access at the next check.
A missing/invalid token returns 401; a valid token without the pool grant returns 401 invalid_auth.
#Admin auth
Admin endpoints (/v1/admin/...) require Authorization: Bearer <OCTOPOOL_ADMIN_TOKEN>. The comparison is constant-time. If no admin token is configured, admin endpoints return 503 admin_unconfigured. Admin auth is entirely separate from caller auth — ordinary callers can never reach admin routes.
#Website session auth
The browser dashboard uses GitHub OAuth and an opaque cookie session:
/login/githubredirects to GitHub withread:org,allow_signup=false, and a short-lived signed state mirrored in anHttpOnlystate cookie. The state payload only carries issue time, nonce, and the sanitized dashboard return path./login/github/callbackexchanges the code with GitHub, resolves the user, verifies OpenClaw membership with the configured org verifier token, and creates or refreshes the caller grant for the default login pool by immutable GitHub user id.- A random
octopool_sessioncookie is set (HttpOnly,Secure,SameSite=Lax). Only its SHA-256 hash is stored inweb_sessions; the raw session token is never stored. /dashboardand/v1/dashboardrequire a valid session, a pool grant, anddashboard_role = 'admin'.
Non-admin org members may be valid Octopool callers, but they cannot see pool-wide operator data.
#GitHub-CLI login exchange
POST /v1/login/github-cli turns a local GitHub token into an Octopool caller token. This is what octopool login calls.
Flow:
- The CLI discovers the server with
GET /.well-known/octopool, then chooses the discoveredapi_baseanddefault_poolunless flags override them. - Body carries
github_token(the user'sgh auth token), a hostname-derivedclient_name, and an optionalpool. - The Worker resolves the GitHub user (
GET /user) and verifies that user is a member ofALLOWED_GITHUB_ORGusing the supplied token. - The caller row and requested default-pool grant are created or refreshed by immutable GitHub user id, org, active status, and pool.
- A new caller token (
op_…) is generated and hashed. It replaces only the token for the same caller and client name; sessions for the caller's other machines remain valid. Callers retain at most 16 named sessions, with least-recently-updated sessions retired as new client names are added. The caller row is refreshed with the current login, user id, and verification time. - The plaintext token is returned once, for the CLI to store locally.
Clients are 1-80 hostname-safe characters. New CLI versions send the local hostname and can override it with --client; older clients use legacy. Audit rows retain the matched caller-token id so stats can separate machines without storing caller token values.
#Pool restriction
octopool login cannot self-grant an arbitrary pool. The requested pool must equal DEFAULT_LOGIN_POOL (default maintainers); anything else is 403 pool_denied. Binding by user id, not the mutable login, keeps later username changes attached to the same caller row.
#Org verification tokens
Two helpers query the user's visible organizations through GitHub GraphQL:
- During login, with the user's own token.
- During background freshness checks, with the configured
OCTOPOOL_GITHUB_ORG_TOKEN.
The query paginates organization memberships and confirms the configured org by login. A completed query without the org denies (403 org_member_denied); transport, credential, rate-limit, or malformed-response failures surface as 502 org_verification_failed. Using the GraphQL budget keeps org verification independent from REST core quota consumed by release and repository workflows. If the verifier token is unset, verification returns 503 org_verification_unavailable.