You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When the vMCP has an embedded auth server, ValidateToken() fails fatally on OIDC discovery before the in-process key provider is ever consulted. The local key provider (getKeyFromLocalProvider) is correctly wired up and would resolve keys from the embedded auth server's signing keys in-memory, but ensureOIDCDiscovered() is a hard gate in ValidateToken() that runs first and returns an error when the issuer URL doesn't resolve (e.g., inside a cluster where the external-facing hostname isn't routable).
PR #4502 added the local key provider infrastructure for the runner/proxy runner. PR #4526 wired keyProvider into the vMCP OIDC middleware. However, neither PR modified ensureOIDCDiscovered() to be non-fatal when a local key provider is available — so the plumbing is complete but the gate was never relaxed.
Steps to reproduce
Deploy a vMCP with an embedded auth server where incoming_auth.oidc.issuer is set to an external-facing URL (e.g., a tunnel hostname)
The external URL is not resolvable from inside the cluster
Send a request with a valid JWT signed by the embedded auth server
Token validation fails with OIDC discovery failed: ... no such host
Expected behavior
The local key provider (tier 1) should resolve the signing key in-process without requiring HTTP-based OIDC discovery. ensureOIDCDiscovered() should not be a fatal gate when a keyProvider is configured and can satisfy the request.
Actual behavior
ValidateToken() (pkg/auth/token.go:1046) calls ensureOIDCDiscovered(), which attempts an HTTP GET to {issuer}/.well-known/openid-configuration. This fails because the hostname doesn't resolve inside the cluster. The error is returned at line 1047, and getKeyFromJWKS() — which correctly tries getKeyFromLocalProvider() first at line 888 — never executes.
Call chain:
ValidateToken()
→ ensureOIDCDiscovered() // issuer is set → HTTP discovery → fails → return error
→ getKeyFromJWKS() // NEVER REACHED
→ getKeyFromLocalProvider() // NEVER REACHED (would have succeeded)
Additional context
Workaround: Setting jwksUrl explicitly in the OIDC config causes ensureOIDCDiscovered() to short-circuit at token.go:769 (v.jwksURL != ""), allowing getKeyFromJWKS() → getKeyFromLocalProvider() to proceed.
Suggested fix: Make ensureOIDCDiscovered() non-fatal when keyProvider is present. For example, in ValidateToken():
The runner path avoids this because it may not set the issuer when the embedded auth server is present, so ensureOIDCDiscovered() is a no-op (short-circuits at line 761).
Bug description
When the vMCP has an embedded auth server,
ValidateToken()fails fatally on OIDC discovery before the in-process key provider is ever consulted. The local key provider (getKeyFromLocalProvider) is correctly wired up and would resolve keys from the embedded auth server's signing keys in-memory, butensureOIDCDiscovered()is a hard gate inValidateToken()that runs first and returns an error when the issuer URL doesn't resolve (e.g., inside a cluster where the external-facing hostname isn't routable).PR #4502 added the local key provider infrastructure for the runner/proxy runner. PR #4526 wired
keyProviderinto the vMCP OIDC middleware. However, neither PR modifiedensureOIDCDiscovered()to be non-fatal when a local key provider is available — so the plumbing is complete but the gate was never relaxed.Steps to reproduce
incoming_auth.oidc.issueris set to an external-facing URL (e.g., a tunnel hostname)OIDC discovery failed: ... no such hostExpected behavior
The local key provider (tier 1) should resolve the signing key in-process without requiring HTTP-based OIDC discovery.
ensureOIDCDiscovered()should not be a fatal gate when akeyProvideris configured and can satisfy the request.Actual behavior
ValidateToken()(pkg/auth/token.go:1046) callsensureOIDCDiscovered(), which attempts an HTTP GET to{issuer}/.well-known/openid-configuration. This fails because the hostname doesn't resolve inside the cluster. The error is returned at line 1047, andgetKeyFromJWKS()— which correctly triesgetKeyFromLocalProvider()first at line 888 — never executes.Call chain:
Additional context
jwksUrlexplicitly in the OIDC config causesensureOIDCDiscovered()to short-circuit attoken.go:769(v.jwksURL != ""), allowinggetKeyFromJWKS()→getKeyFromLocalProvider()to proceed.ensureOIDCDiscovered()non-fatal whenkeyProvideris present. For example, inValidateToken():ensureOIDCDiscovered()is a no-op (short-circuits at line 761).