From 5b98a875fb71e5794f34b83319f8a0db6ffa529d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2026 19:49:23 +0000 Subject: [PATCH 1/3] Update stacklok/toolhive to v0.61.0 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/upstream-projects.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/upstream-projects.yaml b/.github/upstream-projects.yaml index eaea19bf..4c47fb23 100644 --- a/.github/upstream-projects.yaml +++ b/.github/upstream-projects.yaml @@ -64,7 +64,7 @@ projects: - id: toolhive repo: stacklok/toolhive - version: v0.60.0 + version: v0.61.0 # toolhive is a monorepo covering the CLI, the Kubernetes # operator, and the vMCP gateway. It also introduces cross- # cutting features that land in concepts/, integrations/, From ed7f297265543fed50f5a1c8d260aa4a13ee370c Mon Sep 17 00:00:00 2001 From: "stacklok-octosts[bot]" <327033170+stacklok-octosts[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2026 19:50:48 +0000 Subject: [PATCH 2/3] Refresh reference assets for toolhive v0.61.0 --- .../toolhive/reference/helm/operator-crds.mdx | 6 +-- docs/toolhive/reference/helm/operator.mdx | 18 ++++---- static/api-specs/toolhive-api.yaml | 41 ++++++++++++------- 3 files changed, 39 insertions(+), 26 deletions(-) diff --git a/docs/toolhive/reference/helm/operator-crds.mdx b/docs/toolhive/reference/helm/operator-crds.mdx index 5eb606b5..a3a2ce3b 100644 --- a/docs/toolhive/reference/helm/operator-crds.mdx +++ b/docs/toolhive/reference/helm/operator-crds.mdx @@ -11,9 +11,9 @@ Control CRD installation and preservation with these Helm values. This reference lists values declared in the chart's default `values.yaml`. Templates can also accept optional settings and inherited `global` values. -Chart: `toolhive-operator-crds`. Chart version: `0.60.0`. Source: -[`values.yaml`](https://github.com/stacklok/toolhive/blob/v0.60.0/deploy/charts/operator-crds/values.yaml) -at `v0.60.0`. +Chart: `toolhive-operator-crds`. Chart version: `0.61.0`. Source: +[`values.yaml`](https://github.com/stacklok/toolhive/blob/v0.61.0/deploy/charts/operator-crds/values.yaml) +at `v0.61.0`. ## Values diff --git a/docs/toolhive/reference/helm/operator.mdx b/docs/toolhive/reference/helm/operator.mdx index 87ddc172..cf28352a 100644 --- a/docs/toolhive/reference/helm/operator.mdx +++ b/docs/toolhive/reference/helm/operator.mdx @@ -10,9 +10,9 @@ Configure the ToolHive operator with these Helm values. This reference lists values declared in the chart's default `values.yaml`. Templates can also accept optional settings and inherited `global` values. -Chart: `toolhive-operator`. Chart version: `0.60.0`. Source: -[`values.yaml`](https://github.com/stacklok/toolhive/blob/v0.60.0/deploy/charts/operator/values.yaml) -at `v0.60.0`. +Chart: `toolhive-operator`. Chart version: `0.61.0`. Source: +[`values.yaml`](https://github.com/stacklok/toolhive/blob/v0.61.0/deploy/charts/operator/values.yaml) +at `v0.61.0`. ## Values @@ -73,7 +73,7 @@ features: gc: gogc: 75 gomemlimit: 110MiB -image: ghcr.io/stacklok/toolhive/operator:v0.60.0 +image: ghcr.io/stacklok/toolhive/operator:v0.61.0 imageDiscovery: enabled: false resources: {} @@ -158,8 +158,8 @@ serviceAccount: labels: {} name: toolhive-operator tolerations: [] -toolhiveRunnerImage: ghcr.io/stacklok/toolhive/proxyrunner:v0.60.0 -vmcpImage: ghcr.io/stacklok/toolhive/vmcp:v0.60.0 +toolhiveRunnerImage: ghcr.io/stacklok/toolhive/proxyrunner:v0.61.0 +vmcpImage: ghcr.io/stacklok/toolhive/vmcp:v0.61.0 volumeMounts: [] volumes: [] ``` @@ -318,7 +318,7 @@ Key containing the PEM-encoded CA certificate in the Secret. **Type:** `string` -**Default:** `"ghcr.io/stacklok/toolhive/operator:v0.60.0"` +**Default:** `"ghcr.io/stacklok/toolhive/operator:v0.61.0"` Container image for the operator. @@ -334,7 +334,7 @@ Image pull policy for the operator container and image-discovery containers. **Type:** `string` -**Default:** `"ghcr.io/stacklok/toolhive/proxyrunner:v0.60.0"` +**Default:** `"ghcr.io/stacklok/toolhive/proxyrunner:v0.61.0"` Container image for ToolHive proxy runners created by the operator. @@ -342,7 +342,7 @@ Container image for ToolHive proxy runners created by the operator. **Type:** `string` -**Default:** `"ghcr.io/stacklok/toolhive/vmcp:v0.60.0"` +**Default:** `"ghcr.io/stacklok/toolhive/vmcp:v0.61.0"` Container image for Virtual MCP Server (vMCP) deployments created by the operator. diff --git a/static/api-specs/toolhive-api.yaml b/static/api-specs/toolhive-api.yaml index 1c4763ce..3c4a01a5 100644 --- a/static/api-specs/toolhive-api.yaml +++ b/static/api-specs/toolhive-api.yaml @@ -268,7 +268,7 @@ components: ExchangeRefreshTokens enables refresh-token issuance through token exchange for this client. Presence of the block enables it; a nil block refuses both grant kinds. Valid only with restricted, and requires - APIAudience. + SubjectTokenAudiences and offline_access or offline in scopes_supported. properties: id_jag_max_lifetime: description: |- @@ -285,10 +285,13 @@ components: properties: audiences: description: |- - Audiences are the RFC 8707 resource values this client may request a - token for. Required, and must be a subset of RunConfig.AllowedAudiences: - a declared client must not receive every allowed audience just because - this was left empty. + Audiences are output only: the upper bound on the audiences of the + access tokens this client can obtain through any grant. They must be a + subset of RunConfig.AllowedAudiences: a declared client must not receive + every allowed audience just because this was left empty. Required, unless + token_exchange.exchange_refresh_tokens is set and token exchange is the + client's only grant (grant_types omitted, or just the token-exchange + grant), in which case it may be empty. items: type: string type: array @@ -381,19 +384,14 @@ components: register at runtime (DCR or CIMD), so startup cannot check that they exist. A configured delegate client cannot be listed: its tokens carry its own client_id, so listing it would trust everything it accepts. - Valid only with restricted; empty means the client's own tokens only, - except that exchange_refresh_tokens requires a non-empty list and does - not accept the client's own tokens. + Valid only with restricted. On plain token exchange, empty means the + client's own tokens only. The exchange-grant path (exchange_refresh_tokens) + accepts only the listed source clients, never the client's own tokens, so + the list must be non-empty there. items: type: string type: array uniqueItems: false - api_audience: - description: |- - APIAudience is the client's own API audience. The client may request it - even when the subject token does not cover it. It must be one of the - client's Audiences. Valid only with restricted. - type: string exchange_refresh_tokens: $ref: '#/components/schemas/authserver.DelegateClientExchangeRefreshTokens' self_issued_subject_tokens: @@ -404,6 +402,21 @@ components: this client does not lift that restriction. With any, the client may exchange any self-issued token. type: string + subject_token_audiences: + description: |- + SubjectTokenAudiences are the audiences a self-issued subject token must + carry, matched exactly: the token must contain at least one of them. The + check applies on every exchange path, including the client's own tokens. + Listing a value grants nothing: the client can receive that audience only + if its audiences also cover it, and startup refuses that while + exchange_refresh_tokens is set. Each entry must be one of the server's + allowed_audiences. Valid only with restricted, and required with + exchange_refresh_tokens. An empty list is the same as an omitted one: + no requirement. + items: + type: string + type: array + uniqueItems: false required: - self_issued_subject_tokens type: object From 5188a8d5c5ef1fe2b1bb86def156da6bce0e2cc4 Mon Sep 17 00:00:00 2001 From: "stacklok-octosts[bot]" <327033170+stacklok-octosts[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2026 19:55:55 +0000 Subject: [PATCH 3/3] Apply prettier and eslint fixups to skill output --- .../guides-vmcp/delegate-agent-identity.mdx | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/docs/toolhive/guides-vmcp/delegate-agent-identity.mdx b/docs/toolhive/guides-vmcp/delegate-agent-identity.mdx index d522798d..dbb05ffe 100644 --- a/docs/toolhive/guides-vmcp/delegate-agent-identity.mdx +++ b/docs/toolhive/guides-vmcp/delegate-agent-identity.mdx @@ -268,6 +268,13 @@ registration and rejects a replayed `client_assertion` (the same `jti` presented twice) on the second attempt. ToolHive also accepts the token endpoint URL as the `aud` value, or an `aud` array that contains either value. These forms are deprecated, and a future release will require the issuer as the only audience. + +ToolHive returns HTTP 401 `invalid_client` for every rejected assertion: a bad +signature, an unknown `kid`, a wrong `aud`, a reused `jti`, an expired +assertion, an `nbf` or `iat` in the future, or a lifetime longer than the server +allows. A request that sets `client_assertion_type` but omits `client_assertion` +gets HTTP 400 `invalid_request`. + Everything else about the exchange, subject-token validation against `trustedIssuers`, actor resolution, and the resulting `act` claim, works exactly as in the pre-provisioned case above; the issued token's `act.sub` is the @@ -303,12 +310,13 @@ must register again. ## Troubleshooting -| Error | Likely cause | -| -------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `invalid_client` (exact hint varies by cause: wrong secret, wrong `token_endpoint_auth_method`, or a public client attempting a confidential-only grant) | The delegate client must be confidential. Verify `inboundGrants.tokenExchange.delegateClients[].clientSecretRef` is set and the client authenticated with the matching secret and method. | -| `invalid_grant`: "The subject token does not authorize this client to act on behalf of the subject." | The subject token's `may_act.sub` doesn't match the actor ToolHive resolved for this request. | -| `invalid_grant`: "This client is not authorized to exchange subject tokens from the external actor's issuer." | The authenticated client isn't in `issuerPolicies[].allowedDelegateClients` for the issuer that minted the subject token. | -| `invalid_grant`: "The subject token was issued to a different client." | The subject token's own `client_id` claim doesn't match the authenticated delegate client, and the client isn't in `allowedDelegateClients`. | -| `invalid_request`: "The subject token is invalid or could not be verified." | The subject token failed signature, issuer, or audience validation against every configured `trustedIssuers` entry. | -| CRD rejected: `issuerPolicies[]: allowedDelegateClients: Required value` | An `inboundGrants.tokenExchange.issuerPolicies` entry is missing `allowedDelegateClients`. Add it. | -| CRD rejected: "delegateClients with an HTTP issuer require insecureAllowConfidentialOverLoopbackHTTP to be explicitly enabled" | A delegate client uses a plain-HTTP issuer. Use an `https://` issuer, or for local development set `insecureAllowConfidentialOverLoopbackHTTP: true` with a loopback issuer. | +| Error | Likely cause | +| -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `invalid_client` (exact hint varies by cause: wrong secret, wrong `token_endpoint_auth_method`, or a public client attempting a confidential-only grant) | The delegate client must be confidential. Verify `inboundGrants.tokenExchange.delegateClients[].clientSecretRef` is set and the client authenticated with the matching secret and method. | +| HTTP 401 `invalid_client` for a `private_key_jwt` client | The `client_assertion` failed validation. Check that it's signed with a key whose `kid` is in the registered JWKS, that `aud` is the issuer, that `exp`, `nbf`, and `iat` are valid against the server clock, and that each request uses a fresh `jti`. | +| `invalid_grant`: "The subject token does not authorize this client to act on behalf of the subject." | The subject token's `may_act.sub` doesn't match the actor ToolHive resolved for this request. | +| `invalid_grant`: "This client is not authorized to exchange subject tokens from the external actor's issuer." | The authenticated client isn't in `issuerPolicies[].allowedDelegateClients` for the issuer that minted the subject token. | +| `invalid_grant`: "The subject token was issued to a different client." | The subject token's own `client_id` claim doesn't match the authenticated delegate client, and the client isn't in `allowedDelegateClients`. | +| `invalid_request`: "The subject token is invalid or could not be verified." | The subject token failed signature, issuer, or audience validation against every configured `trustedIssuers` entry. | +| CRD rejected: `issuerPolicies[]: allowedDelegateClients: Required value` | An `inboundGrants.tokenExchange.issuerPolicies` entry is missing `allowedDelegateClients`. Add it. | +| CRD rejected: "delegateClients with an HTTP issuer require insecureAllowConfidentialOverLoopbackHTTP to be explicitly enabled" | A delegate client uses a plain-HTTP issuer. Use an `https://` issuer, or for local development set `insecureAllowConfidentialOverLoopbackHTTP: true` with a loopback issuer. |