stacklok / stacklok/toolhive

Gate inbound DCR (/oauth/register) behind an initial access token

Open
#6,425 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement needs-triage security
Dominant language
Go
Stars
2.2k
Forks
300
Avg merge
1d 15h
Merged PRs (30d)
184

Description

Problem

RegisterClientHandler (pkg/authserver/server/handlers/dcr.go, added in #3428) has no authentication or authorization check on the inbound /oauth/register endpoint. Whether a caller can register a client is controlled only by two booleans, AllowConfidentialClientRegistration and AllowPrivateKeyJWTRegistration — not by who's calling. When either is enabled, any caller who can reach the endpoint can self-register a client and immediately authenticate as it: a confidential client gets an unattested secret handed back, and a private_key_jwt client just submits its own public key and proves possession of the matching private key it generated itself.

Neither case is proof that the caller is a legitimate or trusted party — it only proves "the same caller as last time." BaselineClientScopes limits what a self-registered client can do, but that's a separate, additive control, not a substitute for gating the registration call itself. Left open, this lets anyone farm the endpoint for unlimited client creation (credential-stuffing surface, storage/audit-log growth, and a foothold for any future bug in scope logic).

Why this matters now

Comparing against how mature authorization servers handle this:

  • RFC 7591 §3 defines the initial-access-token mechanism specifically to let an AS "limit registration to only previously authorized parties." Separately, §2.3 defines software statements for the same purpose.
  • Okta, Auth0, and Keycloak all default DCR to closed, requiring an initial access token or a trusted software statement.
  • Entra ID goes further: there's no anonymous self-registration concept at all. App creation always requires an authenticated principal, and key/cert association is always performed by an already-authenticated owner/admin, never inline at registration.

ToolHive's own codebase already has the right building blocks — InitialAccessTokenFile/InitialAccessTokenEnvVar/SoftwareStatement (pkg/authserver/config.go) — but they're wired only for the outbound direction (ToolHive as a client doing DCR against an upstream AS). The inbound /oauth/register endpoint has no equivalent.

Proposed fix

Add an optional InitialAccessToken gate to the inbound DCR path, reusing the existing RFC 7591 §3 pattern already present for outbound DCR:

  • When configured, RegisterClientHandler requires a valid bearer token (the initial access token) before accepting a registration request.
  • When unset, behavior is unchanged (opt-in, no behavior change for existing deployments).
  • Consider whether this should also support a trusted software-statement path as a middle ground for deployments that want open self-registration without a shared bearer token.

Scope

This is pre-existing and affects both AllowConfidentialClientRegistration and AllowPrivateKeyJWTRegistration equally — it's not specific to either auth method. It surfaced during review of the private_key_jwt DCR work but is not a regression from that change and is not a blocker for it.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with RegisterClientHandler in pkg/authserver/server/handlers/dcr.go and inspect the existing InitialAccessTokenFile, InitialAccessTokenEnvVar, and SoftwareStatement configuration in pkg/authserver/config.go. Compare the inbound path with the existing outbound DCR initial-access-token handling, then define and test bearer-token validation for configured deployments while preserving current behavior when unset. Resolve whether software statements belong in this change before implementation.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
api, authentication, backend, security
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.