Repository navigation
feat(identitybroker): bump ToolHive to v0.61.0 and close the delegate's access-token path - #2675
Merged
Merged
Conversation
…'s access-token path The broker's delegate client at hosted AS-1 now names the Mecatl API audience only as an input (subject_token_audiences) and has no output audience, so it can never obtain a Mecatl API access token. Un-skip the access-token and offline_access refusal tests, and expect 401 invalid_client for every failed client assertion. Closes #2607 Co-authored-by: Mecatl <noreply@mecatl.dev>
…n_offline_access ToolHive v0.61.0 refuses a subject token issued without offline_access with subject_token_offline_access. The broker now knows that reason and treats it as a configuration fault, as for subject_token_audience; the design's reason tables list it. The delegate tests now pin each refusal: an access-token request gets 400 invalid_request naming the missing output audience, next to a passing control; a token without offline_access gets subject_token_offline_access; and a new test refuses a subject token for another audience with subject_token_audience. Part of #2607 Co-authored-by: Mecatl <noreply@mecatl.dev>
mecatui requests an advertised --oidc-scopes list exactly, and since ToolHive v0.61.0 AS-1 converts at session open only an access token issued with offline_access. A list without it now refuses startup instead of failing every session open with INTERNAL. The access-token refusal test pins 400 invalid_request and only logs ToolHive's free-text description, which has no reason code. Part of #2607 Co-authored-by: Mecatl <noreply@mecatl.dev>
jhrozek
approved these changes
Nov 3, 2026
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2607
Problem
#2653 registered the identity broker's client,
mecatl, at hosted AS-1. The broker can now convert a user's sign-in into a refresh token at session open, but ToolHive v0.60.0 left three gaps:mecatlhad to list the Mecatl API in itsapi_audienceand its outputaudiences. That also let it exchange the same token for a fresh Mecatl API access token. The design forbids this (credentials-and-lifecycle.md, step (3)):mecatlmust be able to hold only authority for gateway calls. This is the last open "Done when" item in Register the broker client at AS-1 and point the broker at it #2607 (Configured delegate client with api_audience can exchange for API access tokens toolhive#7026).server_error, which the broker treats as a retryable outage. A reusedjtigot 400jti_known. The design expects 401invalid_clientfor every failed assertion (Authorization server: refuse every failed private_key_jwt client assertion with 401 invalid_client toolhive#7028).offline_accesson the access token it converts (Authorization server: exchange only access tokens issued with offline_access into refresh tokens toolhive#7029).ToolHive v0.61.0 fixes all three. Its fix for the first gap also changes the delegate client's configuration in a way that breaks compatibility:
api_audienceis replaced by an input-onlysubject_token_audiences.Solution
task tidyacross all modules.internal/identitybroker/adapter/as1server/delegate.go).mecatlnow lists the Mecatl API audience only intoken_exchange.subject_token_audiences(Separate delegate input audiences from output audiences toolhive#7037), and has no outputaudiences. It can still turn a user's Mecatl API token into an exchange-grant refresh token. It can no longer obtain a Mecatl API access token, because ToolHive refuses to start if an output audience covers an input one.offline_accesswith a reason mecatl didn't know,subject_token_offline_access, so the broker dropped it and reported a generic internal error. The broker now knows the reason and treats it as a configuration fault, likesubject_token_audience:mecatuialways requestsoffline_access. The design's reason tables list it too.offline_access.mecatuirequests an advertised--oidc-scopeslist exactly. Before this change, such a deployment would have failed every session open withINTERNALafter the bump. With hosted AS-1,mecak8snow refuses to start if the list lacksoffline_access. The operating guide states the requirement.implementation.md's ToolHive work list. One item stays "Partly": the accepted assertionaudstill includes the token-endpoint URL, which #7028 left optional.Testing
Changes to existing tests:
TestDelegateCannotObtainAPIAccessTokensnow runs. It sends a plain RFC 8693 request for an access token, both with noaudienceand withaudienceset to AS-1's. Each gets 400invalid_request. ToolHive gives this refusal no reason code, so its free-text description, which names the missing output audience, is logged rather than matched. A control subtest shows the same client, assertion and subject token convert with the session-open form, so the refusal comes from the policy. With v0.60.0's configuration the request succeeded.TestDelegateRequiresOfflineAccessnow runs and expects the reasonsubject_token_offline_access.TestDelegateRefusesAForeignAudienceSubjectToken: a subject token for another audience AS-1 issues is refused withsubject_token_audience. This covers the move fromapi_audiencetosubject_token_audiences.serverrefusal_test.goandsessionopen_test.goincludesubject_token_offline_access(session open answersINTERNAL).TestIdentityBrokerAS1FlagRuleshas two new cases:--oidc-scopes=openid,profilerefuses startup, and--oidc-scopes=openid,offline_accessstarts.TestDelegateRefusesBadAssertionsnow expects 401invalid_clientin every case: expired,expmore than 5 minutes ahead, wrong audience, unregistered key, and reusedjti.Runs:
go test -racepasses for./internal/identitybroker/...,./cmd/mecak8s/,./internal/adapter/mcpbroker/...and./internal/adapter/../internal/apppasses with the identity-broker tests selected.go vetpasses fore2e/k8s(with thekind_e2etag),environment/microvmandintegration/microvm.task tidyleaves no changes.task lintshows no new findings. It still stops at the existing darwin-only gosec finding inenvironment/microvm/launch_ownership_darwin.go:754.task docs:checkis clean.API compatibility
No wire change. The one externally visible difference is at AS-1's token endpoint: a token-exchange request from
mecatlfor an access token is now refused. Mecatl never makes that request.