Files
sherlock/docs/auth-model.md
T
Alexandru MacocianandCopilot 6ecff7cf68
Charlie/project-charlie: Build image / config (push) Successful in 1s
Charlie/project-charlie: Deploy runners / config (push) Successful in 0s
Charlie/project-charlie: Build image / build (push) Skipped
Charlie/project-charlie: Deploy runners / deploy-morgott (push) Skipped
Charlie/project-charlie: Deploy runners / deploy-melina (push) Skipped
Charlie/project-charlie: Deploy stack / config (push) Successful in 1s
Charlie/project-charlie: Deploy stack / deploy (push) Skipped
Release / release (push) Successful in 5m4s
Document headless auth modes
Describe the client-credentials grant and static-token mode in the auth
model and configuration docs, and add commented examples to the example
config.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5749c447-6ecd-46bd-946d-21b4d101d084
2026-07-30 16:17:01 +02:00

2.9 KiB

Auth model

Sherlock is a service-keyed OAuth wallet. There is no sherlock login and no master session: each MCP authenticates for the service it calls.

Lifecycle

For a service such as gitea, grafana, gssh, or searxng, the MCP asks internal/authn for a token on first use:

  1. Fresh wallet entry: return it.
  2. Stale entry with refresh token: refresh under $XDG_RUNTIME_DIR/sherlock.refresh.lock, persist, return.
  3. Missing or unrecoverable entry: run a PKCE browser flow on 127.0.0.1:6990, serialized by $XDG_RUNTIME_DIR/sherlock.login.lock, persist, return.

Long-running MCPs use TokenSource and TokenHolder so requests always read the latest bearer. Refresh follows access-token expiry, never opens a browser, and falls back to a fresh login on the next invocation if refresh can no longer recover.

Headless (machine) auth

Interactive login needs a browser and an OS keyring, neither of which exists in a container. Two config-driven modes cover unattended deployments (e.g. jarvis):

  • Client-credentials grant (grant = "client_credentials"): the MCP mints a bearer non-interactively from a username plus an app password, so no browser is ever opened. Authentik's machine-to-machine flow authenticates the account by username + app password, so the issued JWT carries that account's identity and group claims — resource servers (Grafana [auth.jwt], the gssh gateway) validate it exactly like an interactive token. There is no refresh token; renewal re-mints under the login lock.
  • Static token (token_env): the escape hatch for providers with no usable machine grant. Gitea's OAuth2 server is authorization-code-only, so gitea-mcp reads a scoped personal access token from the environment and sends it under the token <PAT> scheme, bypassing OAuth. Identity is unchanged — the PAT belongs to the same account — only the credential is static.

Set SHERLOCK_KEYRING=memory in headless mode to swap the OS keyring for a process-local store: bearers vanish with the process and are re-minted on the next run rather than persisted to a container filesystem. See configuration.md for the config fields.

Service identity

The config supplies issuer, client ID/secret, and base URL. Scopes are owned by each MCP because they follow the tool surface, not the deployment.

Gitea uses Gitea's OAuth2 server. Grafana, Gssh, and SearXNG normally reuse the shared Authentik sherlock-cli provider. Tokens are stored and refreshed per service and are not reused across unrelated services.

User controls

sherlock status lists stored sessions. sherlock logout clears all sessions; sherlock logout <service> clears one.

Sherlock does not share sessions between operators or perform RFC 8693 token exchange. Static service-account tokens are used only in the headless static- token mode above, and only where a provider offers no machine grant.