{"schema":"threatline.record/2","record":{"id":"CVE-2026-111761","aliases":["GHSA-q78c-5jjq-57g8"],"summary":"A2A Java SDK has Server-Side Request Forgery (SSRF) in push-notification delivery","details":"---\n\n- **Ecosystem:** Maven\n- **Affected package:** `org.a2aproject.sdk:a2a-java-sdk-server-common` (push-notification delivery)\n- **Affected versions:** `<= 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's own network position — 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's\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` → `followRedirects(NORMAL)`,\n`VertxA2AHttpClient` → `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's webhook reaches\n  destinations a creation-time allowlist would block, and a `302` on a `POST` is followed as a\n  `GET` — 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→GET 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 — 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://<internal-target>/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's 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's 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","categories":["Libraries & packages"],"product":"org.a2aproject.sdk:a2a-java-sdk-server-common","vendor":"a2aproject","ecosystem":"maven","affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-q78c-5jjq-57g8","source_modified_at":"2026-10-20T21:06:23Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"maven","name":"org.a2aproject.sdk:a2a-java-sdk-server-common"},"range":"< 1.3.0.Final"},{"source":"CVE List","source_url":"https://www.cve.org/CVERecord?id=CVE-2026-111761","source_modified_at":"2026-10-22T21:30:39.670Z","fetched_at":"2026-10-23T18:42:43.628194Z","product":"a2a-java","vendor":"a2aproject","versions":[{"version":"< 1.3.0.Final","status":"affected"}],"defaultStatus":null},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-q78c-5jjq-57g8","source_modified_at":"2026-10-20T21:15:04.059690010Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"name":"org.a2aproject.sdk:a2a-java-sdk-server-common","ecosystem":"Maven","purl":"pkg:maven/org.a2aproject.sdk/a2a-java-sdk-server-common"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.3.0.Final"}]}],"versions":["1.0.0.Alpha4","1.0.0.Beta1","1.0.0.CR1","1.0.0.Final","1.1.0.Final","1.2.0.Final"]}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-q78c-5jjq-57g8","source_modified_at":"2026-10-20T21:06:23Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"maven","name":"org.a2aproject.sdk:a2a-java-sdk-server-common"},"version":"1.3.0.Final"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-q78c-5jjq-57g8","source_modified_at":"2026-10-20T21:15:04.059690010Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"name":"org.a2aproject.sdk:a2a-java-sdk-server-common","ecosystem":"Maven","purl":"pkg:maven/org.a2aproject.sdk/a2a-java-sdk-server-common"},"version":"1.3.0.Final"}],"cvss":5.8,"severity":"Medium","kev":{"listed":false},"epss":{"probability":0.00512,"percentile":0.4285,"score_date":"2026-10-23"},"published_at":"2026-10-20T14:54:46.950Z","modified_at":"2026-10-22T21:30:39.670Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/advisories/GHSA-q78c-5jjq-57g8","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-20T21:06:23Z"},{"source":"CVE List","url":"https://www.cve.org/CVERecord?id=CVE-2026-111761","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-22T21:30:39.670Z"},{"source":"OSV","url":"https://osv.dev/vulnerability/GHSA-q78c-5jjq-57g8","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-20T21:15:04.059690010Z"},{"source":"EPSS","url":"https://api.first.org/data/v1/epss?cve=CVE-2026-111761","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-23"}],"sources":["GitHub Advisory Database","CVE List","OSV","EPSS"],"references":[{"source":"CVE List","url":"https://github.com/a2aproject/a2a-java/security/advisories/GHSA-q78c-5jjq-57g8","tags":["exploit"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-111761","source":"GitHub Advisory Database"},{"source":"CVE List","url":"https://github.com/a2aproject/a2a-java/pull/1096","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/a2aproject/a2a-java/commit/b855b2ae37e4bf23f73e7746ce166fbc5a0b8b5b","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/a2aproject/a2a-java/releases/tag/v1.3.0.Final","tags":["x_refsource_MISC"]},{"url":"https://github.com/advisories/GHSA-q78c-5jjq-57g8","source":"GitHub Advisory Database"}],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"cve_state":"PUBLISHED","ssvc":{"exploitation":"poc","automatable":"yes","technical_impact":"partial","assessed_at":"2026-10-22T21:30:10.957773Z"},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":"ecosystem","confidence":"heuristic","reviewed":false},"change_timestamp":"2026-10-22T21:30:39.670Z","content_hash":"ed0cdfbea3466f2a6abb424333057b14be7641c2d72b2980b768470388877cbe"}}
