stacklok / stacklok/toolhive

Add sub_ns claim to mark a token as backed by a real resolved user

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

Nobody has claimed this yet.

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

Description

Planned work on non-interactive agent credentials needs a way to tell "this subject is a real, resolved human" apart from "this subject is a rotating placeholder we minted because we had nothing else to key a storage row on." Some upstream OAuth providers have no userinfo endpoint, so ToolHive can't resolve a durable identity for that connection and generates a synthetic subject instead. That synthetic subject is UUID-shaped, same as a real one — there's no way to tell them apart by looking at the string.

An earlier design considered inferring trust from the subject's shape (e.g. "no # separator, looks like a UUID, so treat it as native"). That doesn't work — the synthetic and the genuine case produce identically-shaped strings. Any future proposal along those lines should be checked against this before being reconsidered.

Proposal: sub_ns

A URI-named private claim (e.g. https://toolhive.dev/claims/sub_ns) that's a statement about how the token was minted, not an inference from what it contains:

  • Set only on the ResolveUser arm of pkg/authserver/server/handlers/callback.go:117-130 — the one code path that actually resolves a durable identity. Never set for a synthetic flow or an external issuer.
  • Propagated through RFC 8693 token exchange only when the subject token being exchanged is itself self-issued and already carries it. A delegated token only inherits eligibility if the human behind the delegation was themselves a real resolved user.
  • Checked for presence only, never by value, by any consumer. Presence-only means there's nothing to spoof by guessing or crafting a plausible value — the only thing that matters is whether ToolHive's own minter put it there.
  • Added to internalClaims (pkg/auth/context.go:236-239) so middleware can see it from validated raw claims, but excluded from Identity.Claims, webhooks, and audit projections — it's an internal capability flag, not user-facing data.

The credential-read work that consumes this claim runs only for a validated ToolHive-issued token (see the trust-precondition issue) that carries this descriptor — never inferred from descriptor presence on an externally-issued token.

Files
  • pkg/authserver/server/handlers/callback.go:117-130ResolveUser arm, where the claim is minted
  • pkg/auth/context.go:236-239internalClaims
  • token exchange handler — propagation rule (self-issued + already present)

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

Read the ResolveUser arm in pkg/authserver/server/handlers/callback.go:117-130 and internalClaims in pkg/auth/context.go:236-239, then locate the token exchange handler. Done means the claim is minted only for resolved users, propagated only from eligible self-issued subject tokens, and excluded from user-facing projections while remaining available to middleware.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.