Skip to content

feat(utils): add composable UrlValidator for URL screening - #1333

Open
Linux2010 wants to merge 3 commits into
a2aproject:mainfrom
Linux2010:fix/1023-url-validation
Open

Linux2010 wants to merge 3 commits into
a2aproject:mainfrom
Linux2010:fix/1023-url-validation

Conversation

@Linux2010

Copy link
Copy Markdown
Contributor

Description

Implements the UrlValidator sketch from #1023 as the shared, composable URL-screening foundation for both SSRF surfaces called out there (agent card URLs and push-notification webhooks):

  • UrlValidator: parses and (optionally) resolves a URL, runs composable UrlValidationRule objects in order, and returns a ResolvedUrl carrying the addresses the host resolved to — so callers can pin connections to a validated address (DNS-rebinding protection).
  • Built-in rules: RequireScheme and BlockPrivateNetworks (with allow_hosts / allow_cidrs exemptions for deployments that legitimately use private networks). Custom rules are plain UrlValidationRule subclasses.
  • Domain wrappers:
    • PushNotificationUrlValidator — validate_push_notification_url now delegates to the composable stack; its boolean, fail-closed contract is unchanged (all existing SSRF tests pass unmodified).
    • AgentCardUrlValidator — new; validates agent card URLs (http/https on public addresses by default, private networks opt-in).

Scope note: per #1023 this lands the validator as the foundation; wiring the agent card validator into the client resolution path and a connection-pinning transport are follow-ups (the resolved addresses are returned precisely to enable the latter).

Notes for reviewers:

  • Rules and the resolver are injectable, which keeps tests deterministic without touching loop.getaddrinfo.

  • Cancellation is unaffected: asyncio.CancelledError derives from BaseException, so guards on Exception never swallow cancellation.

  • Follow the CONTRIBUTING Guide

  • PR title follows Conventional Commits (feat: → SemVer minor)

  • Tests and linter pass

  • Appropriate docs were updated (module/ABC docstrings state the contract)

Refs #1023 🦕

Validation

  • Claim: UrlValidator rejects non-http(s) schemes and hosts resolving to non-public addresses; validate_push_notification_url keeps its exact accept/reject behavior.
  • Exact head: 20d1a8c
  • BEFORE: push URL screening was push-specific and not composable; agent card URLs had no screening primitive. AFTER: one composable core + two domain wrappers; push behavior identical.
  • Tests: 26 new tests in tests/utils/test_url_validation.py; full suite 2300 passed, coverage 93% (gate 88%).
  • Environment: Python 3.10.20, macOS.
  • Limits / not tested: connection-pinning transport (follow-up per the [Task]: Prepare URL validation infrastructure for agent card and webhook SSRF protection #1023 scope line); wiring AgentCardUrlValidator into the client resolution path (proposed follow-up, happy to add on request).

Prepared with AI assistance (WorkBuddy); reviewed and verified by Linux2010

Implements the UrlValidator sketch agreed on issue a2aproject#1023: a core
validator parses and resolves a URL, runs composable
UrlValidationRule objects (RequireScheme, BlockPrivateNetworks with
allow_hosts/allow_cidrs), and returns a ResolvedUrl that carries the
addresses the host resolved to, so callers can pin connections to a
validated address and guard against DNS rebinding.

Domain wrappers:

- PushNotificationUrlValidator: validate_push_notification_url now
  delegates to the composable stack; its boolean, fail-closed contract
  is unchanged (all existing SSRF tests pass unmodified).
- AgentCardUrlValidator: new, validating agent card URLs (http/https,
  public addresses by default, private networks opt-in).

Rules and the resolver are individually injectable so deployments can
compose custom policies. Issue a2aproject#1023 scopes this to the validator as
the foundation for a2aproject#975 and a2aproject#786.

Refs a2aproject#1023

Signed-off-by: andy <linux2011@qq.com>
@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

🧪 Code Coverage (vs main)

⬇️ Download Full Report

Base PR Delta
src/a2a/server/cluster/database_event_stream.py 92.86% 96.94% 🟢 +4.08%
src/a2a/utils/push_url_validator.py 87.80% 100.00% 🟢 +12.20%
src/a2a/utils/url_validation.py (new) — 99.05% —
Total 93.10% 93.23% 🟢 +0.13%

Generated by coverage-comment.yml

@Linux2010
Linux2010 marked this pull request as draft October 10, 2026 15:09
@Linux2010
Linux2010 marked this pull request as ready for review October 11, 2026 01:04
Self-review follow-ups for the a2aproject#1023 draft:

- expose the address union as public UrlAddress instead of a private
  alias (it appears in the public ResolvedUrl annotation);
- match BlockPrivateNetworks allow_hosts case-insensitively: urlparse
  lowercases parsed hostnames, so configured exemptions must be
  normalized too;
- deduplicate resolved addresses while preserving resolver order
  (getaddrinfo repeats addresses across CNAME chains and record
  families).

Signed-off-by: andy <linux2011@qq.com>
…n_url

PushNotificationUrlValidator already catches InvalidUrlError and logs,
so the wrapper's own try/except was unreachable dead code and showed
up as a -25% coverage regression on the per-file report (spotted by
the coverage bot on this PR).

The wrapper is now a single-line delegation to the domain wrapper,
which owns the logging; a dedicated test module locks the boolean,
fail-closed contract directly.

Signed-off-by: andy <linux2011@qq.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant