{
  "variant_id": "CVE-2026-58196-dcr-metadata-redirect-bypass",
  "created_at": "2026-07-16T08:49:46Z",
  "variant_summary": "Bypass of the v0.31.0 ToolHive SSRF fix via an unguarded Dynamic Client Registration authorization-server metadata re-fetch. The original resource_metadata redirect path is fixed, but the DCR resolver calls oauthproto.FetchAuthorizationServerMetadata(..., nil), whose default client follows a cross-port redirect to a loopback canary.",
  "relation": "newer_version_sibling",
  "origin_kind": "pruva_variant",
  "repository": "https://github.com/stacklok/toolhive",
  "submitted_target": {
    "target_kind": "release_tag",
    "commit_sha": "bc4beddb921523d1f314b82b5b9d4249ee0a605b",
    "version": "0.30.0",
    "ref": "v0.30.0",
    "display": "ToolHive v0.30.0"
  },
  "variant_target": {
    "target_kind": "release_tag",
    "commit_sha": "493d030ed24b8c012db4c5717475a2386f5b72ce",
    "version": "0.31.0",
    "ref": "v0.31.0",
    "display": "ToolHive v0.31.0 fixed release; same variant also reproduced on v0.38.0 commit 116421213382e82016067d32581b61f4fe1ab807"
  },
  "same_root_cause_confidence": "high",
  "same_surface_confidence": "high",
  "claimed_surface": "api_remote",
  "validated_surface": "api_remote",
  "required_entrypoint_kind": "authenticate",
  "required_entrypoint_detail": "ToolHive CLI remote MCP onboarding workflow: thv run <malicious-remote-url> --remote-auth --remote-auth-skip-browser --foreground",
  "attacker_controlled_input": "Remote MCP WWW-Authenticate resource_metadata URL, the RFC 9728 resource metadata document's authorization_servers entry, and a 302 Location returned by the authorization server metadata endpoint during DCR re-discovery.",
  "trigger_path": "remote MCP 401 -> FetchResourceMetadata direct JSON -> authorization_servers validation -> resolveDCRCredentials -> oauthproto.FetchAuthorizationServerMetadata(ctx, discoveredDoc.Issuer, nil) -> unguarded 302 to loopback canary",
  "observed_impact_class": "ssrf",
  "exploitability_confidence": "high",
  "evidence_scope": "production_path",
  "runtime_manifest_present": true,
  "end_to_end_target_reached": true,
  "inferred": false,
  "file_path": "pkg/auth/discovery/discovery.go",
  "line_start": 731,
  "line_end": 731,
  "secondary_anchors": [
    {
      "file_path": "pkg/oauthproto/discovery.go",
      "line_start": 191,
      "line_end": 201
    },
    {
      "file_path": "pkg/oauthproto/discovery.go",
      "line_start": 270,
      "line_end": 285
    },
    {
      "file_path": "pkg/auth/dcr/resolver.go",
      "line_start": 894,
      "line_end": 894
    },
    {
      "file_path": "pkg/networking/http_client.go",
      "line_start": 62,
      "line_end": 80
    }
  ],
  "review_scope_paths": [
    "pkg/auth/discovery/discovery.go",
    "pkg/oauthproto/discovery.go",
    "pkg/auth/dcr/resolver.go",
    "pkg/auth/remote/handler.go",
    "pkg/auth/oauth/oidc.go",
    "pkg/networking/http_client.go",
    "pkg/networking/utilities.go"
  ],
  "artifact_refs": {
    "variant_manifest": "bundle/vuln_variant/variant_manifest.json",
    "validation_verdict": "bundle/vuln_variant/validation_verdict.json",
    "runtime_manifest": "bundle/vuln_variant/runtime_manifest.json",
    "repro_log": "bundle/logs/vuln_variant/reproduction_steps.log",
    "root_cause_equivalence": "bundle/vuln_variant/root_cause_equivalence.json",
    "reproducer": [
      "bundle/vuln_variant/reproduction_steps.sh",
      "bundle/vuln_variant/variant_lab_servers.py"
    ]
  }
}
