Support non-interactive agents reading stored upstream credentials
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 2.2k
- Forks
- 300
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 184
Description
Description
Non-interactive AI agents acting through vMCP need to use a third-party upstream credential (e.g. a GitHub token) that a human already connected via a normal browser login. Today that's structurally impossible: the credential-lookup path requires a tsid (ToolHive's internal session claim), and a delegated RFC 8693 token minted for an agent never carries one (pkg/auth/token.go:1230-1233). The gate isn't a bug — it's the absence of any lookup path that doesn't assume a live session.
This epic adds that path: an explicit, opt-in storage scope that keys a credential by the canonical ToolHive user rather than by session, so the row a browser flow wrote is also the row a delegated agent token can read — gated behind trust checks strict enough that the new read can't be spoofed, and structured so every existing session-based deployment sees zero behavior change.
Completion criteria
- A caller holding a delegated token with no session, backed by a validated real user, reads the exact upstream credential that user connected through an ordinary browser login — proven end to end (the backend's
Authorizationheader is asserted directly, not inferred from absence of error). - Every deployment that never sets
upstreamCredentialScope: platformUserbehaves identically to today, with no new startup failures introduced by stricter issuer validation. - A delegated token without the
sub_nsmarker, from an external issuer, or bound to a synthetic subject is rejected before any storage read. - Concurrent refreshes of a shared row are deduplicated correctly, with a passing concurrency test.
- Cedar authorization never depends on parsing or trusting a claim pulled from a stored upstream credential.
- Coverage runs against a real kind cluster, not just RunConfig-level checks.
Out of scope
- Per-agent credential binding (a structural, per-agent-instance credential rather than one shared row per user+provider). Worth a future scope value; not building it now.
- Mode-switch tooling between
sessionandplatformUser, and any storage migration between them. - A standalone "connect provider X for user U" endpoint independent of a login flow. Investigated — broadening what a login's chain-walk requests covers the initial-connect case using the existing inbound flow.
- The pre-existing ordering gap where
CallToolaggregates (and sends authenticated backend calls) before authorization runs. Real, and this epic extends the same code path to a new caller class, but it predates this work and is being tracked as an accepted, named residual rather than fixed here.
Related
- Modeled on: #6199 (SPIFFE client-auth epic)
- Filed: #6356 (merged), #6403, #6404
- Root blocker:
pkg/auth/token.go:1230-1233
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
Start with pkg/auth/token.go:1230-1233 and the related issues #6356, #6403, and #6404 to understand the delegated-token blocker and existing work. Trace the session-based credential lookup and identify the integration points for the opt-in platformUser scope; done means the stated trust rejections, end-to-end Authorization assertion, refresh deduplication, Cedar safety, and real kind-cluster coverage all pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, kubernetes
- Domain
- authentication, backend-api-design, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100