Skip to content

Commit 5d11a41

Browse files
amirejazclaude
andcommitted
Extend IsClientIDMetadataDocumentURL to accept http://localhost URLs
The embedded AS must resolve CIMD client_ids for local development and testing the same way FetchClientMetadataDocument does — accepting http://localhost and http://127.x.x.x in addition to https://. This brings IsClientIDMetadataDocumentURL in line with validateCIMDClientURL in pkg/oauthproto/cimd/fetch.go which already permits these schemes. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
1 parent 1e0e00f commit 5d11a41

1 file changed

Lines changed: 34 additions & 6 deletions

File tree

pkg/oauthproto/cimd.go

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,39 @@ import "strings"
1212
// be used in production.
1313
const ToolHiveClientMetadataDocumentURL = "https://toolhive.dev/oauth/client-metadata.json"
1414

15-
// IsClientIDMetadataDocumentURL returns true if clientID is an HTTPS URL.
16-
// Any HTTPS URL is treated as a CIMD client_id; DCR-issued IDs are always
17-
// opaque strings that never begin with "https://". Do not tighten this to an
18-
// exact match against ToolHiveClientMetadataDocumentURL — the embedded AS
19-
// must accept CIMD URLs from third-party clients too.
15+
// IsClientIDMetadataDocumentURL returns true if clientID looks like a CIMD URL.
16+
// In production this means any HTTPS URL; in local development http://localhost
17+
// (and http://127.x.x.x) URLs are also accepted so that integration tests can
18+
// use plain httptest.Server instances without TLS setup. DCR-issued IDs are
19+
// always opaque strings that never begin with a URL scheme, so there is no risk
20+
// of false positives. Do not tighten this to an exact match against
21+
// ToolHiveClientMetadataDocumentURL — the embedded AS must accept CIMD URLs
22+
// from third-party clients too.
2023
func IsClientIDMetadataDocumentURL(clientID string) bool {
21-
return strings.HasPrefix(clientID, "https://")
24+
if strings.HasPrefix(clientID, "https://") {
25+
return true
26+
}
27+
// Allow http://localhost and http://127.x.x.x for local development and
28+
// integration testing. These are the only HTTP URLs that
29+
// FetchClientMetadataDocument / validateCIMDClientURL also accept.
30+
if strings.HasPrefix(clientID, "http://") {
31+
return IsLoopbackHost(hostFromURL(clientID))
32+
}
33+
return false
34+
}
35+
36+
// hostFromURL extracts the host (without port) from a raw URL string for the
37+
// narrow purpose of IsClientIDMetadataDocumentURL's loopback check. It avoids
38+
// importing net/url so this leaf package stays import-free. A full URL parse
39+
// is performed by FetchClientMetadataDocument before any network I/O.
40+
func hostFromURL(rawURL string) string {
41+
// Strip scheme
42+
rest := strings.TrimPrefix(rawURL, "http://")
43+
// Extract host (up to first '/', '?', or '#')
44+
for i, c := range rest {
45+
if c == '/' || c == '?' || c == '#' {
46+
return rest[:i]
47+
}
48+
}
49+
return rest
2250
}

0 commit comments

Comments
 (0)