Security Improvement: Add SSRF protection for Push Notification webhooks and authorization checks for Task operations

オープン
#786 コメント 7 件 リアクション 0 件 担当者 1 名 GitHub で見る

@rohityan がすでに取り組んでいます。

2026年3月11日 から。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
45/100
issue の種類
機能追加
明瞭さ
明確に書かれている
活発さ
停滞
技術スタック
python

調査の方向性

Issue の詳細では、2 つのセキュリティ上の問題が説明されています。push notification webhook における SSRF と、task 操作に対する認可の欠如です。まず、記載されているファイルを確認してください。URL の検証については src/a2a/server/tasks/base_push_notification_sender.py、task の認可については src/a2a/server/request_handlers/default_request_handler.py を確認します。データ構造を理解するため、src/a2a/types.pyTask モデルを確認してください。修正では、検証ロジックを追加し、owner フィールドを含むように task モデルを変更したうえで、チェックを強制するよう request handlers を更新します。新しいセキュリティテストに合格し、既存の機能にリグレッションがないことを確認できれば完了です。

索引モデルが issue の本文から書いたものです。

説明

component: server status: needs review

Summary

The current SDK implementation has two architectural gaps that could lead to security issues in production deployments:

  1. Push Notification webhook URLs are used without SSRF protections — any URL provided via PushNotificationConfig is passed directly to httpx.post() with no validation
  2. Task operations have no authorization layer — tasks are looked up by ID only, allowing any client to access, cancel, or modify any other client's tasks

These are not obscure edge cases — they affect the default behavior that every developer inherits when building on this SDK.


Issue 1: SSRF via Push Notification Webhooks

Affected code:

  • src/a2a/server/tasks/base_push_notification_sender.py (lines 53-62)
url = push_info.url  # user-controlled, no validation
response = await self._client.post(
    url,
    json=notification.model_dump(mode="json", exclude_none=True),
    headers=headers,
)

The URL from PushNotificationConfig.url (defined in src/a2a/types.py, line 840) is stored and used without any validation:

  • No scheme restriction (allows file://, gopher://, etc.)
  • No IP/hostname blocklist (allows 127.0.0.1, 169.254.169.254, internal hostnames)
  • No DNS rebinding protection
  • No redirect policy

Both InMemoryPushNotificationConfigStore.set_info() and DatabasePushNotificationConfigStore.set_info() store the URL as-is.

Impact: A malicious client can register a push notification config with an internal URL (e.g., cloud metadata endpoint, internal services) and trigger SSRF when the server sends notifications.

Suggested fix:

  • Validate URL scheme (allow only https://, optionally http://)
  • Resolve the hostname and reject private/loopback IP ranges (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, ::1, link-local)
  • Consider adding a configurable allowlist/blocklist for webhook destinations
  • Disable or limit redirects on the HTTP client

Issue 2: Cross-Client Task IDOR (Missing Authorization)

Affected code:

  • src/a2a/server/request_handlers/default_request_handler.py
    • on_get_task() (line 117)
    • on_cancel_task() (line 131)
    • on_message_send() (line 289)
    • on_resubscribe_to_task() (line 513)
    • on_set_task_push_notification_config() (line 461)
    • on_get_task_push_notification_config() (line 484)
    • on_delete_task_push_notification_config() (line 581)

All task operations retrieve tasks using only the task ID:

task: Task | None = await self.task_store.get(params.id, context)

Although ServerCallContext (defined in src/a2a/server/context.py) is passed through, it is never used for authorization checks. The Task model (src/a2a/types.py, lines 1855-1887) has no owner/user field, making ownership checks impossible even if a developer wanted to add them.

The InMemoryTaskStore.get() and DatabaseTaskStore.get() implementations both look up tasks by ID alone with no authorization logic.

Impact: In any multi-client deployment, Client A can read, cancel, or modify tasks belonging to Client B simply by guessing or enumerating task IDs.

Suggested fix:

  • Add an owner (or client_id) field to the Task model
  • Populate it from ServerCallContext.user when a task is created
  • Check ownership in TaskStore.get() / cancel() / etc., or in DefaultRequestHandler before returning results
  • At minimum, provide a hook or middleware interface so developers can plug in their own authorization logic without forking the SDK

Why this matters for an SDK

While input validation is always partly the developer's responsibility, an SDK/reference implementation sets the pattern that developers follow. Currently:

  • The default path is insecure — a developer has to actively work to add these protections
  • There are no hooks, middleware, or configuration options to enable these protections
  • The official samples and documentation don't warn about these gaps
  • As a reference implementation, this code will be copied and adapted by many downstream projects

Adding basic protections (or at minimum, configurable validation hooks) in the SDK itself would significantly reduce the attack surface across the entire A2A ecosystem.


Environment
  • a2a-python version: latest main branch (commit fa14dbf)
  • Python: 3.12+
主要言語
Python
スター
2.2k
フォーク
496
平均マージ
3日 15時間
マージ済み PR(30日)
20

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

a2aproject/a2a-python のほかの issue

a2aproject/a2a-python の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。