Files
sherlock/docs/auth-model.md
T
amacocian 801bbcec09
CI / build (push) Has been cancelled
Phase 0
2026-05-24 21:53:20 +02:00

3.0 KiB

Auth model

How sherlock obtains and distributes credentials.

The two layers

  1. Operator → Authentik. A single OAuth 2.1 authorization-code + PKCE flow against Authentik (id.alexandru.macocian.me). The broker owns the resulting ID/access/refresh tokens. This is the only browser flow per workstation per session.
  2. Broker → downstream service. For each per-service request from an MCP, the broker mints a service-scoped token using one of the three grant kinds below.

Grant kinds

Set per service in ~/.config/sherlock/services.d/<name>.toml (see service-registry.md).

oidc-federated

The downstream service already federates against Authentik (Gitea, Grafana, Caddy-protected apps). The broker either:

  • Hands the existing Authentik-issued ID token (when the service's expected audience matches), or
  • Performs RFC 8693 token exchange against Authentik's token endpoint, swapping the operator's ID token for a service-scoped access token.

The MCP receives the token in an env var (e.g. GITEA_TOKEN).

own-oauth

The service runs its own OAuth/OIDC stack, not federated through Authentik (think GitHub.com, a SaaS API, …). The broker:

  • Holds a per-(operator, service) OAuth client registration.
  • Runs the auth-code + PKCE flow in a browser the first time, with the callback received on the broker's loopback listener.
  • Caches the refresh token alongside the Authentik creds.
  • Refreshes silently after that.

Triggered explicitly via oauth.consent(service) from sherlock-mcp (Phase 4) or via sherlock login --service <name>.

static-pat

For services that have no OAuth at all, or where the only sane option is a long-lived PAT. The broker reads the PAT from an age-encrypted blob and injects it. Avoid where possible; document why every time it's used.

Token storage

  • Authentik creds + per-service tokens persisted under ~/.config/sherlock/ (encrypted at rest — exact mechanism decided at the start of Phase 1; age-encrypted blob is the leading candidate, libsecret/OS keyring under evaluation).
  • In-memory cache mirrors the on-disk store; the on-disk write is debounced and atomic.
  • Refresh is single-flight: a 401 storm across N MCPs collapses to one refresh.

Audience binding

Per the MCP 2025-06-18 spec, downstream tokens MUST be audience-bound. The broker never hands an MCP a token whose aud claim doesn't name that service. This is the "confused deputy" mitigation the spec calls out.

Scope minimisation

Default service registrations request read-only scopes. Write scopes require either:

  • The operator passing --write to sherlock run (Phase 5), or
  • An explicit scopes override in the service's TOML.

Out of scope for sherlock

  • Issuing SSH certs (gssh handles this — gssh-integration.md).
  • Multi-user / shared tenancy. Sherlock is per-operator, full stop.
  • Acting as a Resource Server itself (Phase 4's sherlock-mcp is a stdio MCP, not HTTP — the agent CLI talks to it over a pipe, no inbound auth).