Skip to content

fix(server): reject push URLs in shared address space - #1326

Open
SashaMIT wants to merge 2 commits into
a2aproject:mainfrom
SashaMIT:fix/push-shared-address-space
Open

SashaMIT wants to merge 2 commits into
a2aproject:mainfrom
SashaMIT:fix/push-shared-address-space

Conversation

@SashaMIT

@SashaMIT SashaMIT commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

validate_push_notification_url rejects a resolved address when ipaddress reports it as private, loopback, link-local, reserved, multicast, or unspecified. 100.64.0.0/10 (RFC 6598 shared address space) is not a public destination: is_global is false. is_private is also false for that range on Python 3.10 through 3.14, so a push URL that resolves to 100.64.0.1 was accepted and the server POSTed the task event there. The same hole exists for the IPv4-mapped form ::ffff:100.64.0.1.

The check now treats that range as blocked, and judges an IPv4-mapped address as the IPv4 address it carries. 100.63.255.255, just below the range, stays allowed.

Test plan

  • Before the range check, test_shared_address_space_blocked and test_ipv4_mapped_shared_address_blocked fail because post is called.
  • After the check, TestPushUrlValidation passes (11 tests), including 100.63.255.255 still allowed.

100.64.0.0/10 is not a public destination, but ipaddress.is_private
is false for it, so the push URL screen accepted a webhook that
resolved there. Judge an IPv4-mapped address as the IPv4 it carries.
@SashaMIT
SashaMIT requested a review from a team as a code owner October 9, 2026 01:30
@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

🧪 Code Coverage (vs main)

⬇️ Download Full Report

Base PR Delta
src/a2a/utils/push_url_validator.py 87.80% 89.13% 🟢 +1.33%
Total 93.11% 93.12% ⚪️ 0.00%

Generated by coverage-comment.yml

@mykytanetipa mykytanetipa left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch on 100.64.0.0/10!

IPv4Address.is_global already excludes that range (self not in _public_network and not self.is_private, where _public_network is 100.64.0.0/10), and is_private implies not is_global. So we can replace addr.is_private with not addr.is_global and drop _SHARED_ADDRESS_SPACE:

    return (
        not addr.is_global
        or addr.is_loopback
        or addr.is_link_local
        or addr.is_multicast
        or addr.is_reserved
        or addr.is_unspecified
    )

is_global is already false for 100.64.0.0/10, so that range stays blocked. Multicast stays blocked too, because is_global can be true there.
@SashaMIT

Copy link
Copy Markdown
Contributor Author

Thanks, that catch is the whole point of the change. is_global is already false for 100.64.0.0/10, so the check is now not is_global, plus loopback, link-local, multicast, reserved, and unspecified. Mapped IPv6 is still judged as the IPv4 inside it.

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.

3 participants