## Summary
ToolHive versions before 0.31.0 perform host-side OAuth/RFC 9728 authentication discovery for remote MCP servers using URLs and redirects influenced by the remote server. During the documented remote MCP onboarding flow (`thv run <URL> --remote-auth`), a malicious remote MCP endpoint can return a `WWW-Authenticate` challenge with `resource_metadata` pointing to a redirect endpoint; vulnerable ToolHive follows that redirect from the host process to a separate loopback/internal canary. ToolHive 0.31.0 blocks the same cross-host/cross-port redirect, so the internal canary is not reached.

## Impact
- Package/component affected: `github.com/stacklok/toolhive`, specifically remote MCP server authentication discovery in `pkg/auth/discovery` and `pkg/auth/remote` as invoked by the ToolHive CLI remote-server workflow.
- Affected versions: all versions before 0.31.0. The reproduction uses ToolHive v0.30.0 as the vulnerable build and v0.31.0 as the fixed negative control.
- Risk level and consequences: CWE-918 server-side request forgery. A malicious or compromised remote MCP server can cause the ToolHive host process, outside the intended per-server container isolation boundary, to fetch attacker-selected internal URLs. In a real deployment this can expose internal HTTP services or metadata endpoints reachable from the host.

## Impact Parity
- Disclosed/claimed maximum impact: host-side SSRF during remote MCP server authentication discovery.
- Reproduced impact from this run: host-side HTTP GET from ToolHive v0.30.0 to an attacker-selected loopback canary URL after an attacker-controlled RFC 9728 `resource_metadata` endpoint issues a 302 redirect.
- Parity: `full`.
- Not demonstrated: no secrets were targeted or exfiltrated. The lab intentionally used a local canary and recorded only request path, headers, and redirect behavior.

## Root Cause
In vulnerable ToolHive v0.30.0, `FetchResourceMetadata(ctx, metadataURL)` in `pkg/auth/discovery/discovery.go` constructs a normal `http.Client` for a server-supplied RFC 9728 resource metadata URL. That client has no `CheckRedirect` policy and no private/loopback/link-local dial guard. Therefore, after the remote MCP endpoint returns `WWW-Authenticate: Bearer ... resource_metadata="<attacker URL>"`, ToolHive fetches the attacker metadata URL and automatically follows HTTP 30x redirects to a different host/port. This occurs in the host-side authentication discovery path, before/outside any per-server container sandbox.

The v0.31.0 fix changes the discovery clients to enforce redirect and private-address protections: `DetectAuthenticationFromServer` and metadata/OIDC discovery use `networking.SameHostRedirectPolicy()`, `FetchResourceMetadata` accepts a `blockPrivateIPs` setting, and the protected HTTP transports can use `networking.NewPrivateIPBlockingDialContext()`. The same-host redirect policy refuses redirects to a different host:port and HTTPS-to-HTTP downgrades, which prevents the demonstrated cross-port loopback canary request.

## Reproduction Steps
1. Run `bundle/repro/reproduction_steps.sh`.
2. The script:
   - Uses the prepared project cache when available (`<project_cache_dir>/repo` and `<project_cache_dir>/bin`) and downloads exact ToolHive release binaries for v0.30.0 and v0.31.0 if not already cached.
   - Starts a malicious remote MCP HTTP endpoint and a separate loopback canary HTTP endpoint in a closed lab network.
   - Invokes the real product workflow twice per version: `thv run <malicious-url> --remote-auth --remote-auth-skip-browser --foreground`.
   - The malicious MCP endpoint returns a `WWW-Authenticate` header with an attacker-controlled `resource_metadata` URL. That metadata endpoint returns a 302 redirect to the separate canary.
   - Records per-attempt ToolHive, malicious endpoint, and canary logs under `bundle/logs/`, and writes `bundle/repro/runtime_manifest.json` before exit.
3. Expected evidence:
   - ToolHive v0.30.0: both vulnerable attempts reach the malicious endpoint and the canary receives one ToolHive host-side request per attempt.
   - ToolHive v0.31.0: both fixed attempts reach the malicious endpoint but the canary remains untouched.

## Evidence
- Main run log: `bundle/logs/reproduction_steps.log`
  - Shows two vulnerable successes and two fixed negative controls:
    - `vulnerable attempt 1: malicious requests=6, canary requests=1`
    - `vulnerable attempt 2: malicious requests=6, canary requests=1`
    - `fixed attempt 1: malicious requests=6, canary requests=0`
    - `fixed attempt 2: malicious requests=6, canary requests=0`
    - `[CONFIRMED] CVE-2026-58196 reproduced with vulnerable/fixed divergence.`
- Vulnerable ToolHive product log: `bundle/logs/toolhive_vulnerable_attempt1.log`
  - Confirms the real `thv run` remote MCP workflow and host-side discovery path:
    - `Attempting to run remote MCP server: http://127.0.0.1:<port>`
    - `Detected authentication requirement from server` with `resource_metadata` set to the attacker endpoint.
    - `Fetching resource metadata` for that attacker URL.
- Malicious endpoint log: `bundle/logs/malicious_server_vulnerable_attempt1.log`
  - Records the initial ToolHive request to `/`, the RFC 9728 metadata redirect at `/metadata-redirect/vulnerable/1`, and the redirect `location` pointing at the separate canary URL.
- Canary log: `bundle/logs/canary_vulnerable_attempt1.log`
  - Records the vulnerable host-side SSRF oracle:
    - `method: GET`
    - `path: /internal-canary/vulnerable/1/metadata.json`
    - `Referer: http://127.0.0.1:<malicious-port>/metadata-redirect/vulnerable/1`
    - `User-Agent: Go-http-client/1.1`
- Fixed product logs: `bundle/logs/toolhive_fixed_attempt1.log`, `bundle/logs/malicious_server_fixed_attempt1.log`, and `bundle/logs/canary_fixed_attempt1.log`
  - The fixed build reaches the malicious remote endpoint but the corresponding canary log stays empty, demonstrating the v0.31.0 redirect/private-dial protection blocks the canary fetch.
- Structured evidence manifest: `bundle/repro/runtime_manifest.json`.
- Attempt summary: `bundle/artifacts/toolhive-ssrf/attempts.jsonl`.

## Recommendations / Next Steps
- Upgrade ToolHive to v0.31.0 or later.
- Keep same-host redirect restrictions and private/loopback/link-local dial blocking on every HTTP client that fetches URLs derived from remote MCP server responses, including RFC 9728 metadata, OIDC discovery, and dynamic client registration discovery.
- Add regression tests for: `resource_metadata` redirects to different host/port, redirects from public targets to private/loopback/link-local addresses, HTTPS-to-HTTP downgrade redirects, and DNS/private-IP rebinding during discovery.
- Ensure future remote discovery features explicitly distinguish operator-configured trusted URLs from server-supplied URLs and apply stricter SSRF guards to the latter.

## Additional Notes
- Idempotency confirmation: `bundle/repro/reproduction_steps.sh` was run twice consecutively after finalization and succeeded both times with the same vulnerable/fixed divergence.
- The lab does not contact real cloud metadata or third-party services. It uses a local malicious endpoint and a local canary as a safe stand-in for an internal service.
- The script attempts to bind a public-looking lab address for the remote endpoint, but if the sandbox cannot add/bind that address it falls back to loopback. The fixed-version negative control remains valid because v0.31.0 refuses the cross-host/cross-port redirect before the canary request is made.
