GHSA-Q78C-5JJQ-57G8
Vulnerability from github – Published: 2026-10-20 21:06 – Updated: 2026-10-20 21:06- Ecosystem: Maven
- Affected package:
org.a2aproject.sdk:a2a-java-sdk-server-common(push-notification delivery) - Affected versions:
<= 1.2.0.Final(and1.2.1.Final-SNAPSHOTonmainprior 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
302from the attacker's webhook reaches destinations a creation-time allowlist would block, and a302on aPOSTis followed as aGET— so GET-only endpoints such as the IMDS credentials path are reachable even though the sender only issuesPOSTs. 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:
- Start a local listener standing in for an internal target (no real metadata service is touched).
- Send one
SendMessagethat starts a long-running task, with the push config inlined and itsurlset tohttp://<internal-target>/latest/meta-data/iam/security-credentials/. UsereturnImmediately: true, headerA2A-Version: 1.0, and roleROLE_USER. The URL is stored verbatim. (Equivalent viaCreateTaskPushNotificationConfigon an existing task.) - 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
{
"affected": [
{
"package": {
"ecosystem": "Maven",
"name": "org.a2aproject.sdk:a2a-java-sdk-server-common"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.3.0.Final"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-111761"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-20T21:06:22Z",
"nvd_published_at": "2026-10-20T15:17:11Z",
"severity": "MODERATE"
},
"details": "---\n\n- **Ecosystem:** Maven\n- **Affected package:** `org.a2aproject.sdk:a2a-java-sdk-server-common` (push-notification delivery)\n- **Affected versions:** `\u003c= 1.2.0.Final` (and `1.2.1.Final-SNAPSHOT` on `main` prior to the fix)\n- **Patched version:** _(the release that carries the fix)_\n- **CWE:** CWE-918 (Server-Side Request Forgery)\n- **Severity:** High\n- **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)\n\n---\n\n## Summary\n\nAn **unauthenticated** attacker can coerce the a2a-java server into issuing HTTP requests to\narbitrary internal and cloud-metadata endpoints from the server\u0027s own network position \u2014 a\nServer-Side Request Forgery reachable in the default deployment with no credentials, no user\ninteraction, and a single request. The behaviour is the shipped default, not a misconfiguration.\n\n## Details\n\n`BasePushNotificationSender` (in `server-common`) POSTs task data to the URL stored on a task\u0027s\n`TaskPushNotificationConfig` with no validation of the destination: no block on loopback, RFC1918,\nlink-local (`169.254.0.0/16`, including the `169.254.169.254` metadata address), unique-local,\nunspecified, or multicast ranges; no scheme restriction; and no bound on redirects.\n`InMemoryPushNotificationConfigStore.setInfo` stores the URL verbatim and\n`onCreateTaskPushNotificationConfig` does not check it either.\n\nBoth shipped HTTP clients follow redirects (`JdkA2AHttpClient` \u2192 `followRedirects(NORMAL)`,\n`VertxA2AHttpClient` \u2192 `setFollowRedirects(true)`), so a creation-time URL allowlist is bypassable:\nan attacker webhook answers `302 Location: http://169.254.169.254/...` and the client follows it. A\nhostname check is likewise defeated by DNS rebinding. A correct guard must validate the *resolved*\nIP of every hop at connection time, not the URL string.\n\n## Exploitation\n\nDemonstrated end-to-end against a running server (default wiring, unauthenticated caller):\n\n- **Arbitrary internal reachability.** The attacker picks the destination and the server issues\n the request from inside the trust boundary. Delivery to loopback and to a real RFC1918 LAN\n address was exercised at runtime; because the code applies no destination filtering of any kind,\n the same path reaches link-local / metadata (`169.254.169.254`) and other internal ranges\n wherever network routing permits.\n- **Allowlist + method bypass via redirect.** A `302` from the attacker\u0027s webhook reaches\n destinations a creation-time allowlist would block, and a `302` on a `POST` is followed as a\n `GET` \u2014 so GET-only endpoints such as the IMDS credentials path are reachable even though the\n sender only issues `POST`s. This is why a URL allowlist is not a sufficient fix; the guard must\n validate the resolved IP of every redirect hop.\n- **Data push into internal services.** On direct (non-redirected) delivery, each request carries\n the task payload in the body, so internal services that ingest POST data receive\n attacker-influenced content (basis for `I:L`). On a redirected request the body is dropped in the\n POST\u2192GET conversion, so the body-push and the redirect-reach are separate capabilities, not\n simultaneous on one request.\n\nThe sender discards the fetched response and exposes no caller-visible difference between a\nsuccessful and a failed delivery (both paths return without surfacing status or body), so the base\ncase reads back nothing \u2014 confidentiality is scored `C:N`. Read-back is limited, reach is not: the\nattacker still drives fully controlled requests to metadata and internal services and writes data\ninto them.\n\n## PoC\n\nAgainst a running server with push notifications enabled (JSON-RPC transport), using only the real\nproduction classes wired the default way:\n\n1. Start a local listener standing in for an internal target (no real metadata service is touched).\n2. Send one `SendMessage` that starts a long-running task, with the push config inlined and its\n `url` set to `http://\u003cinternal-target\u003e/latest/meta-data/iam/security-credentials/`. Use\n `returnImmediately: true`, header `A2A-Version: 1.0`, and role `ROLE_USER`. The URL is stored\n verbatim. (Equivalent via `CreateTaskPushNotificationConfig` on an existing task.)\n3. As the task emits state/artifact updates, the sender POSTs each one to the stored URL, from the\n server\u0027s own network position:\n\n ```\n POST /latest/meta-data/iam/security-credentials/ HTTP/1.1\n Host: 169.254.169.254\n Content-Type: application/json\n\n {\"artifactUpdate\":{\"taskId\":\"...\",\"artifact\":{\"parts\":[{\"text\":\"Progress update 1/20\"}]}, ...}}\n ```\n\nOne `SendMessage` produced multiple server-side POSTs to the attacker-chosen internal path.\nVerified on `main` (commit `72d220c`, JDK 21) and unchanged on `1.2.0.Final`.\n\n## Remediation\n\nValidate the *resolved* IP of the target and of every redirect hop before connecting: reject\nloopback / RFC1918 / link-local / metadata / unique-local / unspecified / multicast; restrict the\nscheme to `http`/`https`; cap redirects; and resolve-and-pin to defeat DNS rebinding. Enable by\ndefault, with a documented opt-out for deployments that intentionally target internal webhooks. The\nGo A2A SDK ships a dial-time resolved-IP guard of this shape by default.\n\n**Workaround:** until a fixed release, block the host\u0027s outbound egress to the metadata endpoint\nand internal ranges at the network layer.\n\n## References\n\n- CWE-918: Server-Side Request Forgery: https://cwe.mitre.org/data/definitions/918.html",
"id": "GHSA-q78c-5jjq-57g8",
"modified": "2026-10-20T21:06:22Z",
"published": "2026-10-20T21:06:22Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/a2aproject/a2a-java/security/advisories/GHSA-q78c-5jjq-57g8"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-111761"
},
{
"type": "WEB",
"url": "https://github.com/a2aproject/a2a-java/pull/1096"
},
{
"type": "WEB",
"url": "https://github.com/a2aproject/a2a-java/commit/b855b2ae37e4bf23f73e7746ce166fbc5a0b8b5b"
},
{
"type": "PACKAGE",
"url": "https://github.com/a2aproject/a2a-java"
},
{
"type": "WEB",
"url": "https://github.com/a2aproject/a2a-java/releases/tag/v1.3.0.Final"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "A2A Java SDK has Server-Side Request Forgery (SSRF) in push-notification delivery"
}
Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline
Sightings and recorded dates, grouped by day · UTC
Loading timeline…
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.