From 25b31dd3a4474b988ad4c504ee2afe21a2865444 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Mon, 26 Oct 2026 00:04:56 +0000 Subject: [PATCH] fix(adagents): strip surrounding whitespace in normalize_url normalize_url(" https://interchange.io ") returned "interchange.io " because urlparse drops leading whitespace but keeps trailing whitespace in the path, so an adagents.json entry with a padded url did not match its agent. Strip before parsing; _normalize_domain already does the same. Signing paths keep byte-exact canonicalization: canonicalize_target_uri is a signature-base canonicalizer, and the brand.json / adagents entry matches in signing select keys or the signer identity and fail closed. Fixes #1624 Co-Authored-By: Claude Opus 5.5 (1M context) --- src/adcp/adagents.py | 5 +++-- tests/test_adagents_matching.py | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/adcp/adagents.py b/src/adcp/adagents.py index ef487d46a..54a9101b8 100644 --- a/src/adcp/adagents.py +++ b/src/adcp/adagents.py @@ -475,9 +475,10 @@ def normalize_url(url: str) -> str: Paths remain case-sensitive. Query strings and fragments are ignored, and trailing path slashes are removed, preserving the existing matching rules. Only the default port for the supplied scheme is removed (80 for HTTP, - 443 for HTTPS); non-default ports remain significant. + 443 for HTTPS); non-default ports remain significant. Surrounding + whitespace is stripped, so a padded adagents.json ``url`` still matches. """ - parsed = urlparse(url) + parsed = urlparse(url.strip()) authority = parsed.netloc if parsed.hostname is not None: host = parsed.hostname.lower() diff --git a/tests/test_adagents_matching.py b/tests/test_adagents_matching.py index 0fe2559c5..ffa80eb30 100644 --- a/tests/test_adagents_matching.py +++ b/tests/test_adagents_matching.py @@ -45,6 +45,9 @@ def document(url: Any = AGENT, tags: list[str] | None = None) -> dict[str, Any]: "https://SALES.Example.com:443/mcp/", "http://sales.example.com:80/mcp", "https://sales.example.com/mcp/?q=x#fragment", + # #1624: stray whitespace around a listed url must not hide the agent. + " https://sales.example.com/mcp ", + "\thttps://sales.example.com/mcp/\n", ], ) def test_normalization_retains_protocol_and_path_contract(url: str) -> None: @@ -98,3 +101,9 @@ def test_listing_distinguishes_unbound_and_unlisted_and_retains_duplicates() -> matches = find_authorized_agent_entries(unbound, AGENT) assert matches == unbound["authorized_agents"] assert matches[0] is unbound["authorized_agents"][0] + + +def test_normalization_strips_surrounding_whitespace_on_either_side() -> None: + assert normalize_url(" https://interchange.io ") == "interchange.io" + assert normalize_url(" https://interchange.io ") == normalize_url("https://interchange.io") + assert find_authorized_agent_entries(document(), f" {AGENT} ")