Skip to content

Commit e54cc3d

Browse files
bokelleyclaude
andcommitted
fix(auth): drift-guard _A2A_DISCOVERY_PATHS against a2a-sdk route renames
Reviewer flagged that the hardcoded /.well-known/agent-card.json literal could silently leak auth on a renamed route if a2a-sdk's canonical path moves. Two changes: 1. Reference a2a.utils.constants.AGENT_CARD_WELL_KNOWN_PATH directly so the 1.0 path tracks a2a-sdk automatically. Legacy 0.3 alias /.well-known/agent.json stays as a literal (no constant for it). 2. New test_discovery_paths_match_a2a_sdk_routes inspects every path that create_agent_card_routes registers and asserts each is in _A2A_DISCOVERY_PATHS. If a future a2a-sdk version adds a new well-known route (extensions, capability descriptor, etc.), this test fails first — adopters update the frozenset rather than silently 401'ing on the renamed/added route. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent cd297b6 commit e54cc3d

2 files changed

Lines changed: 70 additions & 1 deletion

File tree

src/adcp/server/auth.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,9 +559,20 @@ class BearerTokenAuth:
559559
# satisfied even if a future a2a-sdk refactor merges the routes.
560560

561561

562+
# Canonical 1.0 path is sourced from a2a-sdk's own constant — if a
563+
# future a2a-sdk release renames the well-known URI, the import-time
564+
# reference here lifts to the new value automatically and
565+
# ``test_discovery_paths_match_a2a_sdk_routes`` verifies that the
566+
# frozenset still covers every route ``create_agent_card_routes``
567+
# actually registers. Hardcoding the string would silently leak auth
568+
# on the renamed route until someone notices.
569+
from a2a.utils.constants import ( # noqa: E402 (intentional placement after BearerTokenAuth definition)
570+
AGENT_CARD_WELL_KNOWN_PATH as _A2A_AGENT_CARD_PATH,
571+
)
572+
562573
_A2A_DISCOVERY_PATHS: frozenset[str] = frozenset(
563574
{
564-
"/.well-known/agent-card.json",
575+
_A2A_AGENT_CARD_PATH, # 1.0 canonical: ``/.well-known/agent-card.json``.
565576
"/.well-known/agent.json", # Legacy 0.3 alias retained by enable_v0_3_compat=True.
566577
}
567578
)

tests/test_serve_auth_both.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,64 @@ def test_public_exports_include_new_symbols() -> None:
485485
assert "A2ABearerAuthMiddleware" in srv.__all__
486486

487487

488+
# ===========================================================================
489+
# Structural drift guard: a2a-sdk well-known route renames break loud
490+
# ===========================================================================
491+
492+
493+
def test_discovery_paths_match_a2a_sdk_routes() -> None:
494+
"""Catch silent drift between :data:`_A2A_DISCOVERY_PATHS` and
495+
a2a-sdk's actual agent-card routes. If a future a2a-sdk release
496+
renames ``/.well-known/agent-card.json`` (or removes the v0.3
497+
alias), the frozenset would leave the renamed route
498+
unauthenticated until someone noticed. This test fails first.
499+
500+
Walks ``create_agent_card_routes`` against a real ``AgentCard``
501+
and asserts every registered path is in the frozenset.
502+
"""
503+
from a2a.server.routes import create_agent_card_routes
504+
505+
from adcp.server.a2a_server import _build_agent_card
506+
from adcp.server.auth import _A2A_DISCOVERY_PATHS
507+
508+
handler = _OkHandler()
509+
agent_card = _build_agent_card(
510+
handler,
511+
name="drift-guard",
512+
port=0,
513+
description=None,
514+
version="1.0.0",
515+
extra_skills=None,
516+
advertise_all=False,
517+
push_notifications_supported=False,
518+
)
519+
routes = create_agent_card_routes(agent_card=agent_card)
520+
521+
registered_paths = [r.path for r in routes]
522+
assert registered_paths, "a2a-sdk returned no agent-card routes"
523+
524+
missing = [p for p in registered_paths if p not in _A2A_DISCOVERY_PATHS]
525+
assert not missing, (
526+
f"a2a-sdk registers agent-card route(s) {missing!r} that are NOT in "
527+
f"_A2A_DISCOVERY_PATHS={_A2A_DISCOVERY_PATHS!r}. Update the frozenset "
528+
f"in adcp.server.auth to include the new path(s) — otherwise A2A "
529+
f"discovery silently 401s on the renamed/added route."
530+
)
531+
532+
533+
def test_a2a_agent_card_constant_referenced_directly() -> None:
534+
"""The 1.0 path uses ``a2a.utils.constants.AGENT_CARD_WELL_KNOWN_PATH``
535+
rather than a string literal. If a2a-sdk changes the constant,
536+
our frozenset rebases without code changes. This test pins the
537+
indirection so a future maintainer doesn't accidentally inline
538+
the string."""
539+
from a2a.utils.constants import AGENT_CARD_WELL_KNOWN_PATH
540+
541+
from adcp.server.auth import _A2A_DISCOVERY_PATHS
542+
543+
assert AGENT_CARD_WELL_KNOWN_PATH in _A2A_DISCOVERY_PATHS
544+
545+
488546
# ===========================================================================
489547
# RFC 6750 / RFC 7235 compliance: 401 must carry WWW-Authenticate
490548
# ===========================================================================

0 commit comments

Comments
 (0)