Add sub_ns claim to mark a token as backed by a real resolved user
Nobody has claimed this yet.
- 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
ResolveUserarm ofpkg/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 fromIdentity.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-130—ResolveUserarm, where the claim is mintedpkg/auth/context.go:236-239—internalClaims- token exchange handler — propagation rule (self-issued + already present)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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