diff --git a/.agents/skills/release-a2a/SKILL.md b/.agents/skills/release-a2a/SKILL.md index 207a7feb4..f7ff9c7fa 100644 --- a/.agents/skills/release-a2a/SKILL.md +++ b/.agents/skills/release-a2a/SKILL.md @@ -170,6 +170,18 @@ When applicable: 7. Add Javadoc menu entry to the version yml if not already present. +8. Add a row for the new version to the compatibility table in `docs/content/index.html`. The table is in the + `
` block with heading `Compatibility`. Insert a new `` for the released + version immediately before the `` for `dev (unreleased)`, using the same A2A Protocol and Java columns as + the previous row. Example: + ```html + + 1.3.0.Final + 1.0, 0.3 + 17+ + + ``` + ### Step 3: Bump to next SNAPSHOT ```bash diff --git a/AGENTS.md b/AGENTS.md index 80a118d81..baaf16f9a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -82,6 +82,12 @@ The docs site (`docs/`) is built with [Roq](https://docs.quarkiverse.io/quarkus- - Only edit pages under `docs/content/dev/` — released version folders are frozen snapshots and must not be modified - New versioned folders are created at release time (see RELEASE.md step 9) +**Release announcement posts** live under `docs/content/posts/` (one directory per release). Each post directory must contain: +- `index.adoc` (or `index.md`) — the post content, with `image: announce.png` in the YAML frontmatter +- `announce.png` — the announcement banner image (PNG format, named exactly `announce.png`) + +The `announce.png` filename is required: the home page (`docs/content/index.html`) renders the latest post's image using the `post.image` field resolved from this frontmatter entry, and the Roq site theme uses it as the post cover. + **Version metadata:** Each version has a YAML file in `docs/data/versions/` (e.g. `dev.yml`, `1.1.0.Final.yml`) that defines the label, URL path, sort order, default/dev flags, and sidebar menu. When adding or removing a documentation page, update the `menu` list in `docs/data/versions/dev.yml` accordingly. **Running the docs site locally:** diff --git a/SECURITY.md b/SECURITY.md index 61fee79c1..c8c00bd3b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -53,9 +53,14 @@ only), not a fact about the code to be confirmed or denied. either directly documented in the project's own sources or maintainer-confirmed as of this revision — see [§14](#14-resolved-questions) for the record of what was reviewed and when. -**Version binding:** This threat model is written against the `a2a-java` `main` branch, -version `1.2.1.Final-SNAPSHOT`. A report against a released version *N* should be triaged against the model as -it stood at *N*'s release, not necessarily at `HEAD`. +**Version binding:** This threat model is written against `a2a-java` `1.3.0.Final`. A report against a released +version *N* should be triaged against the model as it stood at *N*'s release, not necessarily at `HEAD`. The four +advisories published for `1.3.0.Final` — +[GHSA-qw47-mcm5-934w](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-qw47-mcm5-934w), +[GHSA-q78c-5jjq-57g8](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-q78c-5jjq-57g8), +[GHSA-9rhm-2h4x-jwmx](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-9rhm-2h4x-jwmx), +[GHSA-x32g-jvvm-4725](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-x32g-jvvm-4725) — have +been incorporated; the sections they affected are annotated with the advisory identifier. **Status:** Maintainer-reviewed. @@ -153,6 +158,10 @@ Once a request is admitted, its `Message`/`Task`/`Part` payload flows: transport execute message content at any point in this pipeline — it is opaque data until it reaches the user's `AgentExecutor`. +Authorization enforcement (when a `TaskAuthorizationProvider` is configured) is applied in +`DefaultRequestHandler` as of 1.3.0.Final — previously it was applied only via the CDI decorator +`AuthorizationRequestHandlerDecorator` *(see [GHSA-qw47-mcm5-934w](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-qw47-mcm5-934w))*. + For **client usage**, remote agent responses (task state, artifacts, streamed events) are deserialized into the `spec/` record types and returned to the calling application as data; they are never executed as code by the client library. @@ -202,7 +211,9 @@ client library. | Knob | Default | Effect on the model | Maintainer stance | | --- | --- | --- | --- | -| Absence of a `TaskAuthorizationProvider` CDI bean | No provider configured (fail-open) | **All** `onGetTask`/`onCancelTask`/`onMessageSend`/etc. operations are permitted for any caller regardless of authentication *(documented, `TaskAuthorizationProvider` Javadoc: "When no implementation is provided, all operations are permitted")* | *(maintainer, §14.1)* Intentionally fail-open for backward compatibility and single-tenant/dev simplicity; production multi-tenant deployments must register a provider (§10) | +| Absence of a `TaskAuthorizationProvider` bean | **Fail-closed** (as of 1.3.0.Final; was fail-open in ≤1.2.x — [GHSA-qw47-mcm5-934w](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-qw47-mcm5-934w)) | All task operations are **denied** by default when no provider is configured; set `a2a.authorization.required=false` to restore the open behavior for development/single-user deployments | *(maintainer)* Fail-closed chosen as the safer default for production deployments | +| `a2a.authorization.required` | `true` | When `false`, restores the pre-1.3.0 fail-open behavior: all task operations are permitted regardless of whether a `TaskAuthorizationProvider` is registered | *(maintainer)* Intended only for development, testing, or fully-trusted single-user deployments | +| `a2a.push-notifications.enabled` | `true` | When `false`, the server rejects all push-notification configuration requests (`onCreateTaskPushNotificationConfig`), preventing SSRF via webhook registration ([GHSA-q78c-5jjq-57g8](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-q78c-5jjq-57g8)) | *(maintainer)* Set to `false` to disable push notifications entirely in deployments that don't need them | | `org.a2aproject.sdk.transport.skipValidation` (and `.jsonrpc./.grpc./.rest.skipValidation`) | `false` | Skips `AgentCardValidator` consistency checks between declared and available transports; a misconfiguration/availability issue, not directly an authz/authn bypass | *(maintainer)* debugging/testing convenience; not security-relevant beyond hiding a misconfiguration warning | | `org.a2aproject.sdk.server.extract.request` / `.extract.response` (OpenTelemetry extras) | `false` | When `true`, full request/response payloads (potentially including sensitive message content) are attached as span attributes *(documented, `extras/opentelemetry/README.md`: "Extracting request/response data may expose sensitive information in traces. Use with caution in production environments.")* | *(documented)* explicitly discouraged in production by the module's own README | | `a2a.request-context.populate-referred-tasks` | `true` | When enabled, referenced task IDs are resolved via `TaskStore` and pushed through the same `TaskAuthorizationProvider.checkRead` gate; disabling it skips that lookup/check for referenced tasks | *(maintainer)* | @@ -303,20 +314,72 @@ scores depend on the specific violation and deployment context. - *Violation symptom:* a client on an incompatible major version being silently processed instead of rejected. - *Severity:* correctness-only. - -### §8a Anticipated vulnerability classes (no disclosed CVEs to date) - -This project has no publicly disclosed CVEs at the time of writing. The classes below are *anticipated* — attack -shapes that the adversary model (§7) and the code paths reviewed in this document make plausible — not a -historical record. They exist so a future report can be judged against a named class rather than argued from -scratch, and so this section itself has something concrete to revise (§12) the first time a real report lands. - -- **Authorization bypass via a missing or misconfigured `TaskAuthorizationProvider`.** The most likely class - given the fail-open default (§9.2) — a multi-tenant deployment that forgets to register a provider, or - registers one with a fail-open ownership policy for unknown owners. *(anticipated)* -- **SSRF via push-notification webhook registration.** Given §9.3, a caller registering a - `TaskPushNotificationConfig` pointing at an internal address (e.g. a cloud metadata endpoint) is the most - concrete, reachable-today attack shape in this codebase. *(anticipated)* +6. **Fail-closed authorization default.** *(Added in 1.3.0.Final — [GHSA-qw47-mcm5-934w](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-qw47-mcm5-934w))* When no + `TaskAuthorizationProvider` is configured, `DefaultRequestHandler` denies all task operations by default. + Authorization enforcement is now applied on both the CDI and builder paths directly in + `DefaultRequestHandler`, eliminating the earlier gap where the builder path bypassed + `AuthorizationRequestHandlerDecorator`. Deployments can opt back to open behavior with + `a2a.authorization.required=false` (intended only for development or fully-trusted single-user deployments). + - *Violation symptom:* an absent or misconfigured `TaskAuthorizationProvider` silently permitting operations + for callers it should deny. + - *Severity:* security-critical for multi-tenant deployments. Indicative CVSS 7.5 (CWE-862 + CWE-1188). +7. **SSRF protection for push-notification webhooks.** *(Added in 1.3.0.Final — [GHSA-q78c-5jjq-57g8](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-q78c-5jjq-57g8))* Push + notification URLs are validated against an SSRF-safe policy before delivery: only `http`/`https` schemes are + accepted, and private-network destinations (RFC 1918, loopback, link-local including IPv4-mapped IPv6 such as + `169.254.169.254`) are blocked. HTTP redirect following is disabled on push-notification POST requests to + prevent bypass via redirect chains. Push notification config storage can be disabled entirely with + `a2a.push-notifications.enabled=false`. + - *Violation symptom:* an attacker-supplied `TaskPushNotificationConfig.url()` causing the server to issue + requests to internal or metadata endpoints. + - *Severity:* security-critical for internet-reachable deployments. Indicative CVSS 5.8 (CWE-918). +8. **API key header allowlist and redirect disable.** *(Added in 1.3.0.Final — [GHSA-9rhm-2h4x-jwmx](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-9rhm-2h4x-jwmx))* The + client-side `AuthInterceptor` validates that `APIKeySecurityScheme.name()` (the header name carrying API key + credentials) matches a safe allowlist before placing credentials in a request header; invalid names are + rejected. Automatic HTTP redirect following is disabled in all HTTP client implementations (JDK, Vert.x, + Android), preventing credential leakage to attacker-controlled redirect destinations. + - *Violation symptom:* a malicious agent's AgentCard naming an arbitrary header causes the client to forward + API key credentials to an attacker-controlled server via redirect. + - *Severity:* moderate. Indicative CVSS moderate range (CWE-522). +9. **Push-notification credential log suppression.** *(Added in 1.3.0.Final — [GHSA-x32g-jvvm-4725](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-x32g-jvvm-4725))* The REST + transport handler no longer logs the raw request body on parse failure. Previously, a protobuf parse error in + `parseRequestBody` caused the full JSON body — which may contain push-notification `token` and + `authentication.credentials` fields — to be written to server logs at `SEVERE` level. The fix restricts error + logging to metadata only (body length and content type). + - *Violation symptom:* push-notification bearer tokens or credentials appearing in server logs after a + malformed request. + - *Severity:* moderate (CWE-532). +10. **Authorization check for `listTasks` operation.** *(Added in 1.3.0.Final)* The `onListTasks` handler now + performs a list-scoped read authorization check before delegating to the task store, consistent with the + checks applied to all other task-reading operations. + - *Violation symptom:* a caller denied read access on individual tasks still being able to enumerate all + tasks via `listTasks`. + - *Severity:* moderate information disclosure. + +### §8a Anticipated vulnerability classes + +Four advisories were published for 1.3.0.Final, converting the first two anticipated classes below into +confirmed historical cases. The remaining classes are still *anticipated* — attack shapes that the adversary +model (§7) and the code paths reviewed in this document make plausible. + +- **Authorization bypass via a missing or misconfigured `TaskAuthorizationProvider`.** *(confirmed and fixed)* + Disclosed as [GHSA-qw47-mcm5-934w](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-qw47-mcm5-934w) + (High, CVSS 7.5). The fail-open default in ≤1.2.x allowed any caller to read, cancel, or modify any task + without authentication. Fixed in 1.3.0.Final by switching to a fail-closed default (§8.6) and moving + enforcement into `DefaultRequestHandler` so both CDI and builder paths are covered. +- **SSRF via push-notification webhook registration.** *(confirmed and fixed)* Disclosed as + [GHSA-q78c-5jjq-57g8](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-q78c-5jjq-57g8) + (Moderate, CVSS 5.8). An unauthenticated caller could register a webhook pointing at internal or metadata + endpoints; the server would POST task data to them. Fixed in 1.3.0.Final with SSRF-safe URL validation and + redirect-following disabled (§8.7). +- **Client credential leakage via attacker-controlled redirect.** *(confirmed and fixed)* Disclosed as + [GHSA-9rhm-2h4x-jwmx](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-9rhm-2h4x-jwmx) + (Moderate). A malicious agent could name an arbitrary HTTP header in its AgentCard's `APIKeySecurityScheme`, + causing the client to forward API key credentials to an attacker-controlled server via redirect. Fixed in + 1.3.0.Final with an allowlist for header names and redirect-following disabled (§8.8). +- **Credential disclosure via error-path logging.** *(confirmed and fixed)* Disclosed as + [GHSA-x32g-jvvm-4725](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-x32g-jvvm-4725) + (Moderate, CWE-532). Push-notification bearer tokens were written to server logs in cleartext on parse failure. + Fixed in 1.3.0.Final by restricting error logging to metadata only (§8.9). - **HTTP header/response-splitting bypass in push-notification delivery.** Would require finding a gap in the CRLF rejection described in §8.1 — the class §8.1 exists specifically to close. *(anticipated)* - **Resource exhaustion via unbounded task/queue creation.** §8.3 bounds a single task's queue and @@ -336,14 +399,15 @@ grant or withhold in-scope status. `return true` with no verification logic; see §3)*. **False-friend:** `AuthenticatedUser.isAuthenticated()` returning `true` reflects only that *some* upstream code populated the context that way; it is not itself a verification step. -2. **No authorization by default.** Absent a `TaskAuthorizationProvider` bean, every `RequestHandler` operation - is permitted for every caller, regardless of identity *(documented, `TaskAuthorizationProvider` Javadoc)*. - This is the single most consequential disclaimed property for any deployment reachable by more than one - user/tenant. -3. **No SSRF protection for push-notification webhooks.** `BasePushNotificationSender` sends an HTTP POST to - whatever URL is stored in a `TaskPushNotificationConfig`, which a caller supplies at registration time. No - allow-list, deny-list, or restriction on internal/link-local/loopback destinations is applied by the SDK - *(maintainer — confirmed against `BasePushNotificationSender.dispatchNotification`)*. +2. **No authorization by default (≤1.2.x only — fixed in 1.3.0.Final).** In versions up to and including + 1.2.0.Final, absent a `TaskAuthorizationProvider` bean, every `RequestHandler` operation was permitted for + every caller regardless of identity ([GHSA-qw47-mcm5-934w](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-qw47-mcm5-934w)). As of 1.3.0.Final the default is + **fail-closed**: operations are denied when no provider is configured. Set `a2a.authorization.required=false` + to restore the open behavior for development or fully-trusted single-user deployments. See §8.6. +3. **No SSRF protection for push-notification webhooks (≤1.2.x only — fixed in 1.3.0.Final).** In versions up + to and including 1.2.0.Final, `BasePushNotificationSender` sent an HTTP POST to whatever URL a caller + supplied in `TaskPushNotificationConfig`, with no destination validation ([GHSA-q78c-5jjq-57g8](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-q78c-5jjq-57g8)). As of 1.3.0.Final, + push-notification URLs are validated against an SSRF-safe policy and redirect following is disabled. See §8.7. 4. **No transport-layer encryption.** TLS/mTLS for any of the three transports is entirely a deployment concern; the SDK neither defaults to nor enforces encrypted transport (§3). 5. **`SecurityScheme` declarations are not enforcement.** **False-friend:** an `AgentCard` declaring @@ -377,18 +441,19 @@ grant or withhold in-scope status. - **Configure authentication** at the runtime/transport layer (Quarkus HTTP security, a custom `CallContextFactory`, mTLS, etc.) before exposing a server to any network the deployer does not fully trust. -- **Configure a `TaskAuthorizationProvider`** for any deployment reachable by more than one user or tenant; the - fail-open default (§9.2) is appropriate only for single-tenant, development, or fully-trusted deployments. - Always obtain `RequestHandler` via CDI injection so the `AuthorizationRequestHandlerDecorator` is applied — - manually building `DefaultRequestHandler.builder().build()` bypasses it (§11). +- **Configure a `TaskAuthorizationProvider`** for any deployment reachable by more than one user or tenant. As + of 1.3.0.Final the default is fail-closed (§8.6, §9.2), so absent a provider all task operations are denied + rather than permitted — but providing a real implementation with proper ownership semantics is still essential + for multi-tenant security. Do not set `a2a.authorization.required=false` in production. - **Use fail-closed ownership semantics** in custom `TaskAuthorizationProvider` implementations for production (deny when ownership data is unknown); reserve `owner == null → allow` for testing/single-user setups *(documented, Javadoc)*. - **Use atomic inserts** for `recordOwnership` in custom `TaskAuthorizationProvider` implementations to avoid the TOCTOU race in §9.8. - **Terminate/enforce TLS** for whichever transports are exposed; the SDK will not do this. -- **Validate or restrict push-notification webhook URLs** before allowing external clients to register them, or - wrap `PushNotificationConfigStore`/the create-config path with a custom SSRF guard — the SDK performs none. +- **Push-notification SSRF protection is provided by the SDK** as of 1.3.0.Final (§8.7), but you can further + restrict the attack surface by setting `a2a.push-notifications.enabled=false` in deployments that do not need + push notifications at all. - **Leave OpenTelemetry request/response extraction disabled** (`-Dorg.a2aproject.sdk.server.extract.request` and `.extract.response`) in production unless the sensitivity of captured data has been reviewed (§5a). - **Treat all `Message`/`Task` content reaching `AgentExecutor` as untrusted** network input; never build shell @@ -428,20 +493,14 @@ framework analogous to what some other projects use to flag insecure defaults. ### §11 Known misuse patterns -- **Assuming an absent `TaskAuthorizationProvider` is "safe by default."** It is the opposite: it means every - operation is permitted for every caller. What it looks like: a multi-tenant deployment where any authenticated - (or even unauthenticated) user can read/cancel/modify any other user's tasks. What to do instead: implement - and register a `TaskAuthorizationProvider` before going to production with more than one user/tenant. -- **Manually instantiating `DefaultRequestHandler.builder().build()` in a CDI-based application.** This bypasses - the CDI-discovered `AuthorizationRequestHandlerDecorator`, silently disabling authorization even though a - `TaskAuthorizationProvider` bean exists. What to do instead: always inject `RequestHandler` via CDI. +- **Setting `a2a.authorization.required=false` in production.** This restores the pre-1.3.0 fail-open behavior + where every operation is permitted for every caller regardless of identity. It is appropriate only for + development, testing, or fully-trusted single-user deployments. What it looks like: a multi-tenant deployment + where any caller can read/cancel/modify any other user's tasks. What to do instead: provide a real + `TaskAuthorizationProvider` implementation with proper ownership semantics. - **Enabling OpenTelemetry request/response extraction in production.** This captures full message content (which may include PII or secrets) into trace spans exported to a collector. What to do instead: enable only in controlled debugging sessions, and review collector access controls first. -- **Allowing arbitrary client-supplied push-notification URLs with no allow-list.** This exposes the server to - SSRF: an attacker registers a webhook pointing at an internal service (e.g., a cloud metadata endpoint) and the - server will POST to it. What to do instead: validate/restrict destination URLs before accepting a - `TaskPushNotificationConfig`. - **Building a custom `TaskAuthorizationProvider` with `owner == null → allow`.** Appropriate only for single-user or test deployments; in production this means any task without recorded ownership (e.g., legacy tasks, or tasks created by a bug) is accessible to everyone. What to do instead: fail closed, and add a @@ -453,15 +512,20 @@ framework analogous to what some other projects use to flag insecure defaults. runtime/deployment concern this SDK deliberately does not implement. - **"`SecurityScheme` records have no associated validation code."** By design — they are declarative metadata per the A2A protocol spec, not an enforcement mechanism; see §9.5. `BY-DESIGN: property-disclaimed`. -- **"Any caller can read/cancel any task when no `TaskAuthorizationProvider` is registered."** This is the - documented, intentional default behavior (§9.2), not an unintended bug. `BY-DESIGN: property-disclaimed`. +- **"Any caller can read/cancel any task when no `TaskAuthorizationProvider` is registered."** As of 1.3.0.Final + this is no longer the default behavior: operations are **denied** when no provider is configured (§8.6). A + report of this behavior on 1.3.0.Final+ is a valid finding unless `a2a.authorization.required=false` has been + set, in which case the open behavior is intentional by the deployer. On ≤1.2.x the behavior was + `BY-DESIGN: property-disclaimed` per §9.2; it was fixed via [GHSA-qw47-mcm5-934w](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-qw47-mcm5-934w). - **"`InMemoryTaskStore`/`InMemoryQueueManager` lose all data on restart."** By design — they are the non-persistent defaults; use the JPA/replicated `extras/*` modules for persistence across restarts. `OUT-OF-MODEL` is not quite right here since it's core behavior, not an unsupported component — treat as `BY-DESIGN: property-disclaimed`, cf. `InMemoryTaskStore` Javadoc. -- **"`BasePushNotificationSender` will POST to any URL, including internal addresses."** Documented gap, not a - new finding — see §9.3; report as `BY-DESIGN: property-disclaimed`. URL validation is permanently a deployer - responsibility since acceptable destinations are deployment-specific. +- **"`BasePushNotificationSender` will POST to any URL, including internal addresses."** As of 1.3.0.Final this + is no longer accurate: push-notification URLs are validated against an SSRF-safe policy and redirect following + is disabled (§8.7). A report of SSRF via push notifications on 1.3.0.Final+ should be evaluated against the + current policy implementation. On ≤1.2.x the behavior was `BY-DESIGN: property-disclaimed` per §9.3; it was + fixed via [GHSA-q78c-5jjq-57g8](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-q78c-5jjq-57g8). - **"An attacker can create unlimited tasks/queues, exhausting server memory."** Documented gap — see §9.6; global rate and resource limits are a deployment concern (reverse proxy, runtime configuration), not an SDK-level control. `BY-DESIGN: property-disclaimed`. @@ -469,15 +533,16 @@ framework analogous to what some other projects use to flag insecure defaults. ### §12 Conditions that would change this model - Addition of a built-in authentication mechanism to `server-common` (would revise §3, §9.1, §9.2, §10). -- Addition of SSRF filtering to `BasePushNotificationSender` or `PushNotificationConfigStore` (would revise - §9.3, §10, §11). +- ~~Addition of SSRF filtering to `BasePushNotificationSender` or `PushNotificationConfigStore`~~ — + **triggered in 1.3.0.Final** ([GHSA-q78c-5jjq-57g8](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-q78c-5jjq-57g8)); §8.7, §9.3, §10, §11a revised accordingly. +- ~~The first confirmed vulnerability report against this project~~ — **triggered in 1.3.0.Final**; four + advisories published (GHSA-qw47-mcm5-934w, GHSA-q78c-5jjq-57g8, GHSA-9rhm-2h4x-jwmx, GHSA-x32g-jvvm-4725). + §8a, §8.6–§8.9, §9.2–§9.3, §11, §11a, §14 revised accordingly. - A new transport, extras module, or reference implementation gaining direct filesystem or process-spawning access (would revise §5, §5a). - Promotion of any `examples/` code into a supported, shipped module (would revise §3, §2's component table). - A vulnerability report that cannot be cleanly routed to one of the §13 dispositions — this signals the model has a gap and should be revised (add the property to §8 or §9) rather than resolved ad hoc. -- The first confirmed vulnerability report against this project — it would convert the corresponding §8a - anticipated class into a documented historical case, and should prompt a review of the rest of §8a. ### §13 Triage dispositions @@ -549,3 +614,23 @@ All questions from the initial draft have been resolved. Answers are recorded he The five items in §8a remain tagged *(anticipated)* rather than promoted to *(maintainer)*: they are forward-looking risk hypotheses, not facts about the code, and are not subject to confirmation in the same sense as the claims above. + +**Wave 4 — 1.3.0.Final advisories (resolved):** + +12. **Fail-open authorization default ([GHSA-qw47-mcm5-934w](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-qw47-mcm5-934w)).** Confirmed: absent a + `TaskAuthorizationProvider`, all task operations were permitted for any caller in ≤1.2.x. Fixed in 1.3.0.Final + by switching to a fail-closed default in `DefaultRequestHandler` and enforcing it on both CDI and builder + paths. §5a, §8.6, §9.2, §10, §11, §11a updated. +13. **SSRF via push-notification webhook ([GHSA-q78c-5jjq-57g8](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-q78c-5jjq-57g8)).** Confirmed: `BasePushNotificationSender` + POSTed to caller-supplied URLs with no destination validation in ≤1.2.x. Fixed in 1.3.0.Final with SSRF-safe + URL validation (scheme allowlist, private-network blocking, IPv4-mapped IPv6 blocking) and redirect-following + disabled. §5a, §8.7, §9.3, §10, §11a updated. +14. **API key credential leakage via redirect ([GHSA-9rhm-2h4x-jwmx](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-9rhm-2h4x-jwmx)).** Confirmed: `AuthInterceptor` used + `APIKeySecurityScheme.name()` as an HTTP header name without validation, and all HTTP clients followed + redirects by default, allowing an attacker-controlled agent to harvest credentials. Fixed in 1.3.0.Final with + an allowlist for API key header names and redirect-following disabled across all HTTP client implementations. + §8.8 added. +15. **Push-notification credentials logged in cleartext ([GHSA-x32g-jvvm-4725](https://github.com/a2aproject/a2a-java/security/advisories/GHSA-x32g-jvvm-4725)).** Confirmed: parse failures + in the REST handler's `parseRequestBody` logged the full JSON request body at `SEVERE` level, exposing push + notification `token` and `authentication.credentials` fields. Fixed in 1.3.0.Final by restricting error + logging to body metadata (length, content type) only. §8.9 added. diff --git a/docs/content/index.html b/docs/content/index.html index 7b6880b88..4d49a6846 100644 --- a/docs/content/index.html +++ b/docs/content/index.html @@ -45,6 +45,26 @@

Quick Install

{#for v in cdi:versions.list}{#if v.defaultVersion}

See the Getting Started guide, or jump directly to the Server Guide and Client Guide for full setup instructions.

{/if}{/for}
+
+

Latest Announcement

+ {#if site.collections.get('posts')} + {#for post in site.collections.get('posts')} + {#if post_index == 0} + + {post.title} +
+

{post.title}

+ {#if post.data.synopsis??} +

{post.data.synopsis}

+ {/if} + Read announcement +
+
+ {/if} + {/for} + {/if} +
+

Compatibility

@@ -71,6 +91,11 @@

Compatibility

+ + + + + diff --git a/docs/content/posts/2026-06-10-a2a-java-sdk-1-0-0-final-released/annouce.png b/docs/content/posts/2026-06-10-a2a-java-sdk-1-0-0-final-released/announce.png similarity index 100% rename from docs/content/posts/2026-06-10-a2a-java-sdk-1-0-0-final-released/annouce.png rename to docs/content/posts/2026-06-10-a2a-java-sdk-1-0-0-final-released/announce.png diff --git a/docs/content/posts/2026-06-10-a2a-java-sdk-1-0-0-final-released/index.md b/docs/content/posts/2026-06-10-a2a-java-sdk-1-0-0-final-released/index.md index 2a877f6fd..883de9a22 100644 --- a/docs/content/posts/2026-06-10-a2a-java-sdk-1-0-0-final-released/index.md +++ b/docs/content/posts/2026-06-10-a2a-java-sdk-1-0-0-final-released/index.md @@ -5,9 +5,10 @@ date: 2026-06-10 tags: ai a2a synopsis: 'A2A Java SDK 1.0.0.Final is now available -- the first GA release of the official Java SDK for the A2A Protocol.' author: kkhan +image: announce.png --- -![A2A Java SDK 1.0.0.Final announcement](annouce.png) +![A2A Java SDK 1.0.0.Final announcement](announce.png) I am pleased to announce the release of [A2A Java SDK 1.0.0.Final](https://github.com/a2aproject/a2a-java/releases/tag/v1.0.0.Final) -- our first GA release. The A2A Java SDK is the official Java implementation of the [Agent2Agent (A2A) Protocol](https://a2a-protocol.org/v1.0.0/specification/), an open standard that enables AI agents to communicate and collaborate regardless of underlying framework, language, or vendor. diff --git a/docs/content/posts/2026-06-29-a2a-java-sdk-1-1-0-final-released/index.md b/docs/content/posts/2026-06-29-a2a-java-sdk-1-1-0-final-released/index.md index c42e9f064..8f52e2ee8 100644 --- a/docs/content/posts/2026-06-29-a2a-java-sdk-1-1-0-final-released/index.md +++ b/docs/content/posts/2026-06-29-a2a-java-sdk-1-1-0-final-released/index.md @@ -5,6 +5,7 @@ date: 2026-06-29 tags: ai a2a synopsis: 'A2A Java SDK 1.1.0.Final is now available -- adding per-user task authorization and a new project website.' author: ehsavoie +image: announce.png --- ![A2A Java SDK 1.1.0.Final announcement](announce.png) diff --git a/docs/content/posts/2026-08-07-a2a-java-sdk-1-2-0-final-released/index.adoc b/docs/content/posts/2026-08-07-a2a-java-sdk-1-2-0-final-released/index.adoc index 3ab293763..b78c1ec10 100644 --- a/docs/content/posts/2026-08-07-a2a-java-sdk-1-2-0-final-released/index.adoc +++ b/docs/content/posts/2026-08-07-a2a-java-sdk-1-2-0-final-released/index.adoc @@ -5,6 +5,7 @@ date: 2026-08-07 tags: ai a2a synopsis: 'A2A Java SDK 1.2.0.Final is now available -- with authorization hardening, stream lifecycle hooks, and improved spec compliance.' author: kkhan +image: announce.png --- image::announce.png[A2A Java SDK 1.2.0.Final announcement] diff --git a/docs/content/posts/2026-08-27-a2a-java-sdk-1-3-0-final-released/announce.png b/docs/content/posts/2026-08-27-a2a-java-sdk-1-3-0-final-released/announce.png new file mode 100644 index 000000000..55a28edfc Binary files /dev/null and b/docs/content/posts/2026-08-27-a2a-java-sdk-1-3-0-final-released/announce.png differ diff --git a/docs/content/posts/2026-08-27-a2a-java-sdk-1-3-0-final-released/index.adoc b/docs/content/posts/2026-08-27-a2a-java-sdk-1-3-0-final-released/index.adoc new file mode 100644 index 000000000..1c536390b --- /dev/null +++ b/docs/content/posts/2026-08-27-a2a-java-sdk-1-3-0-final-released/index.adoc @@ -0,0 +1,231 @@ +--- +layout: post +title: 'A2A Java SDK 1.3.0.Final Released' +date: 2026-08-27 +tags: ai a2a +synopsis: 'A2A Java SDK 1.3.0.Final is now available -- with multi-tenancy support, fail-closed authorization, comprehensive security hardening, and protocol compliance improvements.' +author: ehsavoie +image: announce.png +--- +image::announce.png[A2A Java SDK 1.3.0.Final announcement] + +I am happy to announce the release of link:https://github.com/a2aproject/a2a-java/releases/tag/v1.3.0.Final[A2A Java SDK 1.3.0.Final]. This release introduces multi-tenancy support, switches the authorization model to fail-closed by default, delivers a wave of security hardening, and improves protocol compliance across all transports. + +NOTE: This release contains **breaking changes**. See the <> section for details. + +== What's New + +=== Multi-Tenancy Support + +The headline feature of 1.3.0 is **multi-tenancy** (link:https://github.com/a2aproject/a2a-java/pull/1084[\#1084]), which lets a single A2A server provide different agent behavior per tenant. Each tenant can have its own `AgentExecutor` (business logic) and `AgentCard` (capabilities, skills, metadata). Requests without a recognized tenant automatically fall back to the default beans. + +Two new SPIs in `server-common` define the routing contract: + +* `AgentExecutorRouter` -- resolves an `AgentExecutor` for each request based on the tenant identifier. +* `AgentCardRouter` -- resolves the appropriate `AgentCard` for tenant-specific `getExtendedAgentCard` and public card endpoints. + +A CDI-based implementation is provided in the new `a2a-java-extras-multitenancy` module. Use the `@Tenant` qualifier on CDI producer methods to wire tenant-specific beans: + +[source,java] +---- +@Produces +@Tenant("acme") +public AgentExecutor acmeExecutor() { + return new AcmeAgentExecutor(); +} + +@Produces +@Tenant("acme") +@ExtendedAgentCard +public AgentCard acmeCard() { + return AgentCard.builder().name("Acme Agent")...build(); +} +---- + +The resolved tenant is available in `RequestContext.getTenant()` during execution. Tenant-specific public cards are served at `/.well-known/{tenant}/agent-card.json`. Tenant identifiers are restricted to `a-zA-Z0-9_-.` characters. + +When `a2a-java-extras-multitenancy` is not on the classpath, the server behaves as a single-tenant deployment and no code changes are required. + +This is a **breaking change** -- see <> below. + +=== Fail-Closed Authorization Default + +The authorization model now defaults to **fail-closed** (link:https://github.com/a2aproject/a2a-java/pull/1095[\#1095], link:https://github.com/a2aproject/a2a-java/security/advisories/GHSA-qw47-mcm5-934w[GHSA-qw47-mcm5-934w]). Authorization enforcement has been moved from the CDI-only `AuthorizationRequestHandlerDecorator` directly into `DefaultRequestHandler`, so it is enforced on both the CDI and builder paths. When no `TaskAuthorizationProvider` is configured, all task operations are now denied by default. + +To restore the previous open behavior for single-user deployments or testing: + +[source,properties] +---- +a2a.authorization.required=false +---- + +This is a **breaking change** -- see <> below. + +=== Security Hardening + +This release includes a comprehensive set of security fixes addressing multiple vulnerability classes: + +==== Credential leakage prevention + +The SDK now validates API key header names against a safe allowlist and disables automatic HTTP redirect following in all HTTP clients (JDK, Vert.x, Android). This prevents credential leakage via header injection and cross-origin redirects (link:https://github.com/a2aproject/a2a-java/pull/1097[\#1097], link:https://github.com/a2aproject/a2a-java/security/advisories/GHSA-9rhm-2h4x-jwmx[GHSA-9rhm-2h4x-jwmx]). + +==== SSRF protection for push notifications + +Push notification URLs are now validated against an SSRF-safe policy: only allowed schemes, with private-network blocking including IPv4-mapped IPv6. HTTP redirect following is also disabled on push notification POST requests. A new `a2a.push-notifications.enabled` property gates push config storage (link:https://github.com/a2aproject/a2a-java/pull/1096[\#1096], link:https://github.com/a2aproject/a2a-java/security/advisories/GHSA-q78c-5jjq-57g8[GHSA-q78c-5jjq-57g8]). + +==== Additional hardening + +* **Authorization for listTasks** -- the `onListTasks` handler now performs a list-scoped read check before delegation (link:https://github.com/a2aproject/a2a-java/pull/1038[\#1038]) +* **Error message sanitization** -- internal error messages are now sanitized to prevent information disclosure (link:https://github.com/a2aproject/a2a-java/security/advisories/GHSA-x32g-jvvm-4725[GHSA-x32g-jvvm-4725]) +* **CR/LF injection prevention** -- push notification headers are validated to reject CR/LF characters (CWE-113) (link:https://github.com/a2aproject/a2a-java/pull/1053[\#1053]) + +==== Threat model + +A threat model has been added to link:https://github.com/a2aproject/a2a-java/blob/main/SECURITY.md[SECURITY.md] (link:https://github.com/a2aproject/a2a-java/pull/1090[\#1090]) to document the SDK's attack surface and security assumptions. + +==== Security reporting + +We would like to thank the community members who responsibly reported security issues. If you discover a security vulnerability, please follow the process described in our link:https://github.com/a2aproject/a2a-java/blob/main/SECURITY.md[SECURITY.md] -- your reports help us keep the SDK safe for everyone. + +=== AgentExecutor as Functional Interface + +`AgentExecutor` is now a `@FunctionalInterface` with a default no-op `cancel` method (link:https://github.com/a2aproject/a2a-java/pull/1028[\#1028]). Simple agents that don't need cancellation support can be implemented as lambdas: + +[source,java] +---- +AgentExecutor executor = (context, emitter) -> { + emitter.text("Hello!"); + emitter.done(); +}; +---- + +Existing implementations with an explicit `cancel` override are unaffected. + +=== CDI HTTP Client Provider + +The new `a2a-java-extras-http-client-cdi` module (link:https://github.com/a2aproject/a2a-java/pull/1071[\#1071]) adds a `CdiA2AHttpClientProvider` that resolves a user-provided `A2AHttpClient` bean from CDI at priority 200. The `A2AHttpClientFactory` now sorts providers by priority at class init and iterates in order, catching exceptions per-provider for graceful fallback. + +=== Protocol Compliance + +Several protocol compliance issues have been addressed: + +* **Version and extension validation on all operations** -- previously only enforced on message send paths, now checked on all nine JSON-RPC and REST operations per specification section 3.6.2 (link:https://github.com/a2aproject/a2a-java/pull/1066[\#1066]), and on all gRPC handler methods (link:https://github.com/a2aproject/a2a-java/pull/1042[\#1042]) +* **Part content key validation** -- the JSON-RPC transport now rejects `Part` objects with multiple populated content keys (link:https://github.com/a2aproject/a2a-java/pull/1065[\#1065]) and prefers a populated content key when decoding (link:https://github.com/a2aproject/a2a-java/pull/1061[\#1061]) +* **UnsupportedOperationError** -- streaming endpoints now return `-32004` when the agent card does not advertise streaming support, with a consistent error message across all transports (link:https://github.com/a2aproject/a2a-java/pull/1041[\#1041]) +* **FilePart name field** -- `FilePart` now supports an optional `name` field as mentioned in the spec text (link:https://github.com/a2aproject/a2a-java/pull/1091[\#1091]) + +=== Interoperability Test Kit + +The ITK now uses shared scenarios from the upstream A2A specification (link:https://github.com/a2aproject/a2a-java/pull/1094[\#1094]) and has been upgraded to the v2 ITK launcher (link:https://github.com/a2aproject/a2a-java/pull/1063[\#1063]). + +== Bug Fixes + +* **Enforce task state-machine transitions** -- tasks now follow strict state-machine transition rules, preventing invalid state changes. Cancel operations are serialized to avoid races (link:https://github.com/a2aproject/a2a-java/pull/1045[\#1045]) +* **Per-task push notification config limit** -- the `PushNotificationConfigStore` now enforces a per-task limit on push notification configurations (link:https://github.com/a2aproject/a2a-java/pull/1044[\#1044]) +* **Hardened event consumer and queue** -- event processing is now more resilient to concurrent access patterns (link:https://github.com/a2aproject/a2a-java/pull/1040[\#1040]) +* **Guard limitTaskHistory against negative values** -- `limitTaskHistory` no longer accepts negative `historyLength` values (link:https://github.com/a2aproject/a2a-java/pull/1039[\#1039]) +* **Thread-safe InMemoryPushNotificationConfigStore** -- replaced `synchronizedMap` with `ConcurrentHashMap` using atomic per-key operations and immutable snapshot storage (link:https://github.com/a2aproject/a2a-java/pull/1057[\#1057]) +* **Deferred AgentCard resolution** -- all transport handlers now resolve `AgentCard` lazily to prevent startup failures when the card producer depends on the HTTP server's bound address (link:https://github.com/a2aproject/a2a-java/pull/1058[\#1058]) +* **gRPC transport validation** -- improved validation and error messages in the gRPC transport layer (link:https://github.com/a2aproject/a2a-java/pull/1026[\#1026]) + +[[migration]] +== Migration from 1.2.0.Final + +Update your BOM version: + +[source,xml] +---- + + + + org.a2aproject.sdk + a2a-java-sdk-bom + 1.3.0.Final + pom + import + + + +---- + +This release has two breaking changes: + +[[multitenancy-migration]] +=== 1. Multi-tenancy routing changes DefaultRequestHandler + +The introduction of `AgentExecutorRouter` and `AgentCardRouter` changes the `DefaultRequestHandler` constructor and builder. If you wire `DefaultRequestHandler` programmatically, update to the new API: + +[source,java] +---- +// Single-tenant: unchanged usage +DefaultRequestHandler handler = DefaultRequestHandler.builder() + .agentExecutor(agentExecutor) + .taskStore(taskStore) + .queueManager(queueManager) + // ... + .build(); +---- + +For multi-tenant deployments without CDI, supply an `AgentExecutorRouter` directly on the builder: + +[source,java] +---- +Map executors = Map.of( + "acme", new AcmeAgentExecutor(), + "beta", new BetaAgentExecutor() +); +AgentExecutor defaultExecutor = new DefaultAgentExecutor(); + +AgentExecutorRouter router = tenant -> + executors.getOrDefault(tenant, defaultExecutor); + +DefaultRequestHandler handler = DefaultRequestHandler.builder() + .agentExecutorRouter(router) // replaces .agentExecutor() for multi-tenant + .taskStore(taskStore) + .queueManager(queueManager) + // ... + .build(); +---- + +CDI-based deployments pick up the routers automatically when `a2a-java-extras-multitenancy` is on the classpath. + +`GetExtendedAgentCardRequest` now carries `GetExtendedAgentCardParams` to support tenant-specific card resolution. + +[[auth-migration]] +=== 2. Fail-closed authorization default + +Applications without a `TaskAuthorizationProvider` will now reject all task operations by default. Either: + +* Provide a `TaskAuthorizationProvider` implementation, or +* Set `a2a.authorization.required=false` to restore the previous open behavior + +[source,properties] +---- +# For development/testing or single-user deployments +a2a.authorization.required=false +---- + +== Contributors + +Thank you to the contributors of this release! + +link:https://github.com/ehsavoie[@ehsavoie], link:https://github.com/kabir[@kabir], link:https://github.com/ez-lbz[@ez-lbz], link:https://github.com/JakubWorek[@JakubWorek], link:https://github.com/malladinagarjuna2[@malladinagarjuna2], link:https://github.com/omatheusmesmo[@omatheusmesmo], link:https://github.com/ruilopes[@ruilopes], link:https://github.com/KXH[@KXH] + +== Resources + +* link:https://github.com/a2aproject/a2a-java/releases/tag/v1.3.0.Final[Release Notes on GitHub] +* link:https://central.sonatype.com/artifact/org.a2aproject.sdk/a2a-java-sdk-parent/1.3.0.Final[Maven Central] +* link:https://javadoc.io/doc/org.a2aproject.sdk/[JavaDoc] +* link:https://a2a-protocol.org/v1.0.0/specification/[A2A Specification] +* link:https://a2aproject.github.io/a2a-java/[Project Website] +* link:https://github.com/a2aproject/a2a-java/tree/main/examples[Examples] + +== Come Join Us + +We value your feedback a lot so please report bugs, ask for improvements etc. Let's build something great together! + +If you are an A2A Java SDK user or just curious, don't be shy and join our welcoming community: + +* provide feedback on link:https://github.com/a2aproject/a2a-java/issues[GitHub]; +* craft some code and link:https://github.com/a2aproject/a2a-java/pulls[push a PR]; +* discuss with us in the `#a2a-java` channel on link:https://discord.gg/jTtSkJB74Q[Discord]; diff --git a/docs/web/_custom.css b/docs/web/_custom.css index ac7dcfa80..e7dee1fc9 100644 --- a/docs/web/_custom.css +++ b/docs/web/_custom.css @@ -213,6 +213,75 @@ text-align: left; } +/* ── Latest Announcement banner ──────────────────────────────────────────── */ + +.latest-post-banner { + display: flex; + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 0.75rem; + overflow: hidden; + text-decoration: none; + background: rgba(255, 255, 255, 0.03); + transition: border-color 0.2s ease, box-shadow 0.2s ease; +} + +.latest-post-banner:hover { + border-color: var(--color-accent-400); + box-shadow: 0 4px 20px rgba(63, 81, 181, 0.2); + text-decoration: none; +} + +.latest-post-banner-image { + width: 45%; + object-fit: cover; + flex-shrink: 0; + display: block; +} + +.latest-post-banner-content { + display: flex; + flex-direction: column; + justify-content: center; + padding: 2rem; + gap: 1rem; + flex: 1; +} + +.latest-post-banner-title { + font-size: 1.5rem; + font-weight: 700; + margin: 0; + line-height: 1.3; +} + +.latest-post-banner-synopsis { + font-size: 0.95rem; + opacity: 0.8; + margin: 0; + line-height: 1.6; +} + +.latest-post-banner-cta { + font-size: 0.9rem; + font-weight: 600; + color: var(--color-accent-300); +} + +@media (max-width: 640px) { + .latest-post-banner { + flex-direction: column; + } + + .latest-post-banner-image { + width: 100%; + max-height: 220px; + } + + .latest-post-banner-content { + padding: 1.25rem; + } +} + /* ── Syntax Highlighting (highlight.js) ───────────────────────────────────── */ /* Match the site's monospace font (Roboto Mono) */
1.0, 0.3 17+
1.3.0.Final1.0, 0.317+
dev (unreleased) 1.0, 0.3