stacklok / stacklok/toolhive

Support non-interactive agents reading stored upstream credentials

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

Nobody has claimed this yet.

authentication enhancement go needs-triage security
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 Authorization header is asserted directly, not inferred from absence of error).
  • Every deployment that never sets upstreamCredentialScope: platformUser behaves identically to today, with no new startup failures introduced by stricter issuer validation.
  • A delegated token without the sub_ns marker, 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 session and platformUser, 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 CallTool aggregates (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

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 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.