Skip to content

Release v0.44.0 - #6365

Merged
jerm-dro merged 1 commit into
mainfrom
release/v0.44.0
Aug 18, 2026
Merged

Release v0.44.0#6365
jerm-dro merged 1 commit into
mainfrom
release/v0.44.0

Conversation

@toolhive-release-app

Copy link
Copy Markdown
Contributor

Release v0.44.0

Version Bump

minor release

Files Updated

  • VERSION
  • deploy/charts/operator-crds/Chart.yaml (path: version)
  • deploy/charts/operator-crds/Chart.yaml (path: appVersion)
  • deploy/charts/operator/Chart.yaml (path: version)
  • deploy/charts/operator/Chart.yaml (path: appVersion)
  • deploy/charts/operator/values.yaml (path: operator.image)
  • deploy/charts/operator/values.yaml (path: operator.toolhiveRunnerImage)
  • deploy/charts/operator/values.yaml (path: operator.vmcpImage)
  • Helm chart docs (via helm-docs)

Next Steps

  1. Review this PR
  2. Merge to main
  3. Release automation will handle the rest

Checklist

  • Version bump is correct
  • All CI checks pass

Release-Triggered-By: jerm-dro
@github-actions github-actions Bot added the size/XS Extra small PR: < 100 lines changed label Aug 18, 2026
@codecov

codecov Bot commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 73.00%. Comparing base (af7c176) to head (c5c9e50).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6365      +/-   ##
==========================================
- Coverage   73.03%   73.00%   -0.04%     
==========================================
  Files         742      742              
  Lines       78485    78485              
==========================================
- Hits        57325    57300      -25     
- Misses      17154    17195      +41     
+ Partials     4006     3990      -16     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jerm-dro
jerm-dro merged commit b3df968 into main Aug 18, 2026
76 of 77 checks passed
@jerm-dro
jerm-dro deleted the release/v0.44.0 branch August 18, 2026 16:37
@github-actions

Copy link
Copy Markdown
Contributor

📝 Generated release notes for v0.44.0

Auto-generated by the release-notes skill. Review and, if good, apply with:

gh release edit v0.44.0 --notes-file <paste-below>.md
Click to expand release notes

🚀 Toolhive v0.44.0 is live!

A security-focused release that closes an SSRF vulnerability in the OAuth token and dynamic-client-registration paths, hardens external may_act delegation, and fixes a cross-client token-acceptance hole on the Google tokeninfo path. Token exchange also gains RFC 8693 actor_token support, letting a client assert a specific acting-agent identity.

⚠️ Breaking Changes

  • Google tokeninfo introspection now requires an audience — deployments using https://oauth2.googleapis.com/tokeninfo for introspection with no audience configured will now fail at startup; set an audience to your OAuth client ID (migration guide)
  • External may_act delegation is now opt-in per issuer — a trusted external issuer's may_act claims are rejected unless that issuer sets allow_may_act: true, and combining it with a wildcard delegate-client list is refused at startup (migration guide)
  • INSECURE_DISABLE_URL_VALIDATION no longer reaches private IPs for server-supplied endpoints — operators who relied on that variable to reach a private IdP through a remote server's metadata must now configure the IdP explicitly or opt into private IPs (migration guide)
Migration guide: Google tokeninfo introspection requires an audience

Who is affected: any deployment — CLI or Kubernetes — that configures token introspection against Google's tokeninfo endpoint (https://oauth2.googleapis.com/tokeninfo) without also setting an audience.

Why this changed: Google's tokeninfo endpoint returns no iss claim, so ToolHive synthesised one locally and then compared it against the configured issuer — a self-satisfying check that proved nothing about which OAuth client the token was minted for. The audience check was the only real binding to the deployment, and it was skipped when audience was empty. The net effect was that any valid Google access token was accepted, including one minted for an unrelated, attacker-registered OAuth client. ToolHive now refuses the combination at startup rather than silently accepting cross-client tokens at runtime.

The startup error is:

audience is required when using Google's tokeninfo endpoint for introspection:
without it any valid Google access token is accepted regardless of the OAuth client it was minted for

Before

thv proxy --oidc-issuer https://accounts.google.com \
  --oidc-introspection-url https://oauth2.googleapis.com/tokeninfo \
  <mcp-server>
# MCPOIDCConfig
spec:
  introspectionUrl: https://oauth2.googleapis.com/tokeninfo
  # audience omitted

After

thv proxy --oidc-issuer https://accounts.google.com \
  --oidc-introspection-url https://oauth2.googleapis.com/tokeninfo \
  --oidc-audience <your-google-oauth-client-id> \
  <mcp-server>
# MCPOIDCConfig
spec:
  introspectionUrl: https://oauth2.googleapis.com/tokeninfo
  audience: <your-google-oauth-client-id>

Migration steps

  1. Identify every workload configured with --oidc-introspection-url https://oauth2.googleapis.com/tokeninfo (or the equivalent introspectionUrl CRD field).
  2. Set the audience to the OAuth client ID your tokens are minted for — --oidc-audience on the CLI, or audience in the CRD.
  3. Restart the workload and confirm it starts cleanly. Tokens minted for any other Google OAuth client will now be correctly rejected.

PR: #6236

Migration guide: external may_act delegation is now opt-in

Who is affected: deployments running v0.41.0–v0.43.0 that configured a trusted external OIDC issuer under trusted_issuers and relied on that issuer's may_act claim to authorize delegation. Because no major IdP emits may_act today, real-world impact is expected to be very small — but the semantics did change.

Why this changed: a well-formed may_act claim was authoritative on the external path, bypassing the issuer's allowedActors policy entirely, gated only by a delegate-client list that was permitted to be the wildcard "*". Any external IdP that could be induced to emit a may_act claim — through claim-mapping misconfiguration, or by mapping a user-editable profile field — could therefore name any ToolHive client as delegate. Accepting a foreign issuer's may_act is now an explicit, per-issuer decision.

Three behavioural narrowings land together:

  1. A subject token bearing may_act from an issuer without allow_may_act: true is rejected: subject token from issuer %q carries a may_act claim, but allow_may_act is disabled.
  2. allow_may_act: true combined with allowed_delegate_clients: ["*"] is rejected during config validation, so the auth server fails to start rather than running with that combination.
  3. On the external may_act path, the authenticated client must now appear in allowed_delegate_clients.

Issued-token shape also changes. The act claim now always carries iss alongside sub (previously sub only), on every exchange including single-hop self-issued ones. Prior act hops are rebuilt from only iss and sub rather than re-signed verbatim, the serialized claim is size-bounded, and malformed or over-deep chains are rejected instead of re-minted. Downstream consumers that read additional attributes out of nested act hops will no longer see them.

Before

# auth-server config
trusted_issuers:
  - issuer_url: https://idp.example.com
    expected_audience: https://toolhive.example.com
    allowed_delegate_clients: ["*"]

After

# auth-server config
trusted_issuers:
  - issuer_url: https://idp.example.com
    expected_audience: https://toolhive.example.com
    allowed_delegate_clients: [agent-a]   # named clients — "*" is refused with allow_may_act
    allow_may_act: true                   # explicit opt-in

The same policy is now expressible through the operator CRD:

spec:
  embeddedAuthServer:
    trustedIssuers:
      - issuerUrl: https://idp.example.com
        expectedAudience: https://toolhive.example.com
        allowedDelegateClients: [agent-a]
        allowMayAct: true

Migration steps

  1. Audit every trusted_issuers entry and determine whether you actually depend on that issuer's may_act claim. If you do not, no action is needed — the new default is the safe one.
  2. If you do depend on it, replace any allowed_delegate_clients: ["*"] with the explicit list of ToolHive client IDs permitted to act as delegates.
  3. Add allow_may_act: true to that issuer entry (CRD: allowMayAct: true).
  4. Apply the updated operator-crds chart if you are on Kubernetes — trustedIssuers and allowMayAct are new CRD fields.
  5. If any downstream service parses the act claim, confirm it tolerates the added iss member and does not depend on extra attributes in nested hops.

PR: #6353 — Closes #6323, #6113

Migration guide: INSECURE_DISABLE_URL_VALIDATION no longer widens the private-IP gate

Who is affected: operators who set INSECURE_DISABLE_URL_VALIDATION=true to let ToolHive reach a private or loopback IdP whose endpoint arrives from a remote server's metadata document — for example a registration_endpoint discovered during dynamic client registration.

Why this changed: the guard treated "is this address private?" as the security question, when the real question is who supplied the URL. An operator who configures dex at 127.0.0.1 has decided to trust it; a host that arrived inside a document a remote MCP server served has not been trusted by anyone. Server-supplied endpoints now keep the private-IP restriction regardless of the environment variable.

INSECURE_DISABLE_URL_VALIDATION still relaxes the HTTPS-scheme requirement exactly as before — only the private-IP widening is withdrawn, and only for server-supplied hosts.

Operator-configured localhost and in-cluster IdPs keep working, including on the DCR path: dex, Keycloak-in-Docker, and 10.x/*.internal addresses are covered by the operator's own decision to configure them, and there are non-regression tests for each.

Before

export INSECURE_DISABLE_URL_VALIDATION=true   # relied on to reach a private IdP
thv proxy --remote-auth-issuer <issuer> <mcp-server>

After

# Option A — name the IdP explicitly so it is operator-configured (preferred)
thv proxy --remote-auth-issuer https://idp.internal/realms/main <mcp-server>
# Option B — opt the upstream into private IPs
# auth-server config
allow_private_ips: true

# operator CRD (MCPExternalAuthConfig)
spec:
  allowPrivateIPs: true

Migration steps

  1. Check whether you set INSECURE_DISABLE_URL_VALIDATION=true anywhere, and whether it was load-bearing for reaching a private IdP.
  2. Prefer configuring the IdP's issuer or token URL explicitly — an authorization server naming its own authority in its own metadata stays covered by your decision to configure it.
  3. Where an upstream genuinely needs to reach a private address it did not name, set allow_private_ips: true (CRD: allowPrivateIPs: true) on that upstream.
  4. Note that a token endpoint may no longer redirect cross-host; redirects off the original authority are refused.

PR: #6351

🆕 New Features

  • Clients performing RFC 8693 token exchange can now supply actor_token and actor_token_type at /oauth/token to assert a specific acting-agent identity distinct from their own client ID, while every client-identity policy decision stays bound to the authenticated OAuth client (#6331)

🐛 Bug Fixes

  • Fixed an SSRF vulnerability (GHSA-3768-rwj3-38p2, CWE-918) where a malicious MCP server could steer the OAuth code-for-token exchange or a dynamic client registration at an internal address and receive the authorization code, PKCE verifier, or initial access token — reported by kta1kri (#6351)
  • Google tokeninfo introspection configured without an audience no longer accepts any valid Google access token regardless of which OAuth client minted it (#6236)
  • Claude Desktop's LLM gateway authentication now works on macOS, where it was previously broken in every case because the credential-helper shim invoked a bare thv that GUI-launched apps cannot resolve on launchd's PATH — affected users should re-run thv llm setup (#6354)

🧹 Misc

  • The skills build/publish workflow pushes with --no-sign so it is unblocked until keyless signing lands (#6334)
  • Corrected a stale version comment on the pinned codespell action SHA (#6360)
Full commit log

What's Changed

Full Changelog: v0.43.0...v0.44.0

🔗 Full changelog: v0.43.0...v0.44.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release size/XS Extra small PR: < 100 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant