---
id: "CVE-2026-111761"
kind: "vulnerability"
aliases: ["CVE-2026-111761", "GHSA-q78c-5jjq-57g8"]
title: "A2A Java SDK has Server-Side Request Forgery (SSRF) in push-notification delivery"
severity: "medium"
cvss: 5.800000190734863
epss: 0.005119999870657921
exploited: false
status: "open"
category: "ai-tooling"
products: [{"name": "org.a2aproject.sdk:a2a-java-sdk-server-common", "fixed": ["1.3.0.Final"], "affected": "< 1.3.0.Final", "ecosystem": "maven"}]
ecosystems: ["maven"]
tags: ["a2a-java-sdk", "agent-servers", "ai-tooling", "cwe-918", "java", "maven", "self-hosted"]
not_affected_if: ["egress-filtered", "push-notifications-disabled"]
published_at: "2026-10-20T15:17:11+00:00"
first_seen_at: "2026-10-23T19:11:32+00:00"
updated_at: "2026-10-23T21:57:20+00:00"
sources: [{"source": "ghsa", "url": "https://github.com/advisories/GHSA-q78c-5jjq-57g8", "first_seen_at": "2026-10-23T19:11:32+00:00"}, {"source": "nvd", "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-111761", "first_seen_at": "2026-10-23T21:57:20+00:00"}]
---

# A2A Java SDK has Server-Side Request Forgery (SSRF) in push-notification delivery

The A2A Java SDK server sends task push-notification POSTs to any URL a caller supplies, with no check on destination IP, scheme, or redirects. An unauthenticated attacker can use the server as a proxy to reach loopback, internal network, and cloud metadata endpoints, and to write attacker-influenced data into internal services.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- maven:org.a2aproject.sdk:a2a-java-sdk-server-common affected < 1.3.0.Final fixed 1.3.0.Final

## How to check

Check pom.xml or the dependency lock for org.a2aproject.sdk:a2a-java-sdk-server-common at a version below 1.3.0.Final.
Confirm whether push notifications are in use (TaskPushNotificationConfig).

## Fix

Upgrade org.a2aproject.sdk:a2a-java-sdk-server-common to 1.3.0.Final.
Until then, block the server's outbound egress to 169.254.169.254 and internal ranges at the network layer.

## Timeline

- 2026-10-23T19:11:32+00:00 ghsa https://github.com/advisories/GHSA-q78c-5jjq-57g8
- 2026-10-23T21:57:20+00:00 nvd https://nvd.nist.gov/vuln/detail/CVE-2026-111761

## From the sources

---

- **Ecosystem:** Maven
- **Affected package:** `org.a2aproject.sdk:a2a-java-sdk-server-common` (push-notification delivery)
- **Affected versions:** `<= 1.2.0.Final` (and `1.2.1.Final-SNAPSHOT` on `main` prior to the fix)
- **Patched version:** _(the release that carries the fix)_
- **CWE:** CWE-918 (Server-Side Request Forgery)
- **Severity:** High
- **CVSS v3.1:** `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N` (5.8, Moderate)

---

## Summary

An **unauthenticated** attacker can coerce the a2a-java server into issuing HTTP requests to
arbitrary internal and cloud-metadata endpoints from the server's own network position — a
Server-Side Request Forgery reachable in the default deployment with no credentials, no user
interaction, and a single request. The behaviour is the shipped default, not a misconfiguration.

## Details

`BasePushNotificationSender` (in `server-common`) POSTs task data to the URL stored on a task's
`TaskPushNotificationConfig` with no validation of the destination: no block on loopback, RFC1918,
link-local (`169.254.0.0/16`, including the `169.254.169.254` metadata address), unique-local,
unspecified, or multicast ranges; no scheme restriction; and no bound on redirects.
`InMemoryPushNotificationConfigStore.setInfo` stores the URL verbatim and
`onCreateTaskPushNotificationConfig` does not check it either.

Both shipped HTTP clients follow redirects (`JdkA2AHttpClient` → `followRedirects(NORMAL)`,
`VertxA2AHttpClient` → `setFollowRedirects(true)`), so a creation-time URL allowlist is bypassable:
an attacker webhook answers `302 Location: http://169.254.169.254/...` and the client follows it. A
hostname check is likewise defeated by DNS rebinding. A correct guard must validate the *resolved*
IP of every hop at connection time, not the URL string.

## Exploitation

Demonstrated end-to-end against a running server (default wiring, unauthenticated caller):

- **Arbitrary internal reachability.** The attacker picks the destination and the server issues
  the request from inside the trust boundary. Delivery to loopback and to a real RFC1918 LAN
  address was exercised at runtime; because the code applies no destination filtering of any kind,
  the same path reaches link-local / metadata (`169.254.169.254`) and other internal ranges
  wherever network routing permits.
- **Allowlist + method bypass via redirect.** A `302` from the attacker's webhook reaches
  destinations a creation-time allowlist would block, and a `302` on a `POST` is followed as a
  `GET` — so GET-only endpoints such as the IMDS credentials path are reachable even though the
  sender only issues `POST`s. This is why a URL allowlist is not a sufficient fix; the guard must
  validate the resolved IP of every redirect hop.
- **Data push into internal services.** On direct (non-redirected) delivery, each request carries
  the task payload in the body, so internal services that ingest POST data receive
  attacker-influenced content (basis for `I:L`). On a redirected request the body is dropped in the
  POST→GET conversion, so the body-push and the redirect-reach are separate capabilities, not
  simultaneous on one request.

The sender discards the fetched response and exposes no caller-visible difference between a
successful and a failed delivery (both paths return without surfacing status or body), so the base
case reads back nothing — confidentiality is scored `C:N`. Read-back is limited, reach is not: the
attacker still drives fully controlled requests to metadata and internal services and writes data
into them.

## PoC

Against a running server with push notifications enabled (JSON-RPC transport), using only the real
production classes wired the default way:

1. Start a local listener standing in for an internal target (no real metadata service is touched).
2. Send one `SendMessage` that starts a long-running task, with the push config inlined and its
   `url` set to `http://<internal-target>/latest/meta-data/iam/security-credentials/`. Use
   `returnImmediately: true`, header `A2A-Version: 1.0`, and role `ROLE_USER`. The URL is stored
   verbatim. (Equivalent via `CreateTaskPushNotificationConfig` on an existing task.)
3. As the task emits state/artifact updates, the sender POSTs each one to the stored URL, from the
   server's own network position:

   ```
   POST /latest/meta-data/iam/security-credentials/ HTTP/1.1
   Host: 169.254.169.254
   Content-Type: application/json

   {"artifactUpdate":{"taskId":"...","artifact":{"parts":[{"text":"Progress update 1/20"}]}, ...}}
   ```

One `SendMessage` produced multiple server-side POSTs to the attacker-chosen internal path.
Verified on `main` (commit `72d220c`, JDK 21) and unchanged on `1.2.0.Final`.

## Remediation

Validate the *resolved* IP of the target and of every redirect hop before connecting: reject
loopback / RFC1918 / link-local / metadata / unique-local / unspecified / multicast; restrict the
scheme to `http`/`https`; cap redirects; and resolve-and-pin to defeat DNS rebinding. Enable by
default, with a documented opt-out for deployments that intentionally target internal webhooks. The
Go A2A SDK ships a dial-time resolved-IP guard of this shape by default.

**Workaround:** until a fixed release, block the host's outbound egress to the metadata endpoint
and internal ranges at the network layer.

## References

- CWE-918: Server-Side Request Forgery: https://cwe.mitre.org/data/definitions/918.html

---

_Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai_
