Skip to content

fix!: Update the name of A2A headers#709

Merged
kabir merged 1 commit intoa2aproject:mainfrom
jmesnil:A2A-Version
Mar 10, 2026
Merged

fix!: Update the name of A2A headers#709
kabir merged 1 commit intoa2aproject:mainfrom
jmesnil:A2A-Version

Conversation

@jmesnil
Copy link
Collaborator

@jmesnil jmesnil commented Mar 6, 2026

A2A-Version and A2A-Extensions are specified by the spec (as of dec790a) instead of the X-A2A-*.

Also updates the gRPC context keys to get rid of the prefix.

I did not change the X-A2A-Notification-Token header that is still present in the spec but I'll open a PR to verify if that's not an omission

@jmesnil jmesnil added this to the 1.0.0 milestone Mar 6, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refactors the A2A header naming convention to comply with the latest specification, removing the deprecated 'X-' prefix from A2A-Version and A2A-Extensions headers. The change ensures consistency across the application's various transport layers and internal context handling, improving adherence to modern API design principles.

Highlights

  • Header Renaming: The X-A2A-Version and X-A2A-Extensions HTTP headers have been renamed to A2A-Version and A2A-Extensions respectively, aligning with the latest A2A specification.
  • gRPC Context Key Updates: Corresponding gRPC context keys and metadata handling logic have been updated to reflect the new header names, removing the 'X-' prefix from internal keys.
  • Codebase Alignment: All relevant parts of the codebase, including client transport, common header definitions, and server-side interceptors for gRPC, JSON-RPC, and REST, have been updated to use the new header names.
Changelog
  • client/transport/grpc/src/main/java/io/a2a/client/transport/grpc/GrpcTransport.java
    • Updated gRPC metadata keys and header retrieval to use A2A-Extensions and A2A-Version instead of X-A2A-Extensions and X-A2A-Version.
  • common/src/main/java/io/a2a/common/A2AHeaders.java
    • Renamed X_A2A_VERSION and X_A2A_EXTENSIONS constants to A2A_VERSION and A2A_EXTENSIONS respectively.
  • reference/grpc/src/main/java/io/a2a/server/grpc/quarkus/A2AExtensionsInterceptor.java
    • Updated comments and gRPC metadata key extraction to reference A2A-Version and A2A-Extensions headers.
  • reference/jsonrpc/src/main/java/io/a2a/server/apps/quarkus/A2AServerRoutes.java
    • Updated JSON-RPC server routes to extract protocol version and extensions using the new A2A-Version and A2A-Extensions headers.
  • reference/rest/src/main/java/io/a2a/server/rest/quarkus/A2AServerRoutes.java
    • Updated REST server routes to extract protocol version and extensions using the new A2A-Version and A2A-Extensions headers.
  • transport/grpc/src/main/java/io/a2a/transport/grpc/context/GrpcContextKeys.java
    • Renamed gRPC context keys from x-a2a-version and x-a2a-extensions to a2a-version and a2a-extensions.
  • transport/grpc/src/main/java/io/a2a/transport/grpc/handler/GrpcHandler.java
    • Updated Javadoc for header extraction to reference the A2A-Version header.
Activity
  • No specific activity (comments, reviews, progress updates) has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@jmesnil
Copy link
Collaborator Author

jmesnil commented Mar 6, 2026

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request correctly updates the A2A header names from X-A2A-* to A2A-* across several files, aligning with the latest spec. The changes are consistent and cover constants, gRPC keys, and their usage. I've left a couple of comments pointing out some Javadoc sections that were missed during the update. Addressing these will ensure the documentation is fully consistent with the code changes. Overall, this is a good and necessary update.

@jmesnil
Copy link
Collaborator Author

jmesnil commented Mar 6, 2026

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the A2A header names from X-A2A-* to A2A-* for version and extensions, aligning with the latest specification across client, server, and common modules. However, a critical vulnerability has been identified: the new header names (A2A-Version, A2A-Extensions) contain uppercase letters, which are prohibited in gRPC metadata keys by the gRPC Java library. This will cause an IllegalArgumentException at runtime, leading to a Denial of Service for the gRPC transport. It is recommended to use lowercase names (e.g., a2a-version, a2a-extensions) to ensure gRPC compatibility. Additionally, a performance improvement is suggested for the gRPC interceptor by defining metadata keys as constants.

// Extract A2A protocol version header
Metadata.Key<String> versionKey =
Metadata.Key.of(A2AHeaders.X_A2A_VERSION, Metadata.ASCII_STRING_MARSHALLER);
Metadata.Key.of(A2AHeaders.A2A_VERSION, Metadata.ASCII_STRING_MARSHALLER);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

A critical vulnerability has been identified: the gRPC Metadata.Key.of method requires header names to be all lowercase. Using A2AHeaders.A2A_VERSION (which is A2A-Version) will throw an IllegalArgumentException at runtime, causing the gRPC call to fail and leading to a Denial of Service for the gRPC transport. To address this, ensure that header names passed to Metadata.Key.of are lowercase. Additionally, for better performance and adherence to best practices, Metadata.Key instances should be declared as static final constants within the class rather than being instantiated on each request.

* Used to communicate the protocol version that the client is using.
*/
public static final String X_A2A_VERSION = "X-A2A-Version";
public static final String A2A_VERSION = "A2A-Version";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

This header name contains uppercase letters, which are not allowed in gRPC metadata keys. In gRPC Java, the Metadata.Key.of() method strictly validates that the name is composed only of lowercase alphanumeric characters, hyphens, underscores, or periods. Using this constant in gRPC metadata will cause an IllegalArgumentException at runtime. It is recommended to use lowercase names for all A2A protocol headers to ensure compatibility across all transports, including gRPC, while remaining valid for HTTP-based transports (REST and JSON-RPC) which are case-insensitive.

Suggested change
public static final String A2A_VERSION = "A2A-Version";
public static final String A2A_VERSION = "a2a-version";

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* Used to communicate which extensions are requested by the client.
*/
public static final String X_A2A_EXTENSIONS = "X-A2A-Extensions";
public static final String A2A_EXTENSIONS = "A2A-Extensions";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

This header name contains uppercase letters, which are not allowed in gRPC metadata keys. Using this constant in Metadata.Key.of() will cause an IllegalArgumentException at runtime. It is recommended to use lowercase names for all A2A protocol headers to ensure compatibility across all transports, including gRPC.

Suggested change
public static final String A2A_EXTENSIONS = "A2A-Extensions";
public static final String A2A_EXTENSIONS = "a2a-extensions";

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gemini, you are wrong considering the following documentation https://grpc.github.io/grpc-java/javadoc/io/grpc/Metadata.Key.html

Comment on lines 64 to 65
A2AHeaders.A2A_EXTENSIONS,
Metadata.ASCII_STRING_MARSHALLER);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The gRPC Metadata.Key.of method requires header names to be all lowercase. Using A2AHeaders.A2A_EXTENSIONS (which is "A2A-Extensions") will throw an IllegalArgumentException during class initialization, causing the GrpcTransport to fail to load. This results in a Denial of Service for the client.

Comment on lines 67 to 68
A2AHeaders.A2A_VERSION,
Metadata.ASCII_STRING_MARSHALLER);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The gRPC Metadata.Key.of method requires header names to be all lowercase. Using A2AHeaders.A2A_VERSION (which is "A2A-Version") will throw an IllegalArgumentException during class initialization, causing the GrpcTransport to fail to load. This results in a Denial of Service for the client.

Copy link
Collaborator Author

@jmesnil jmesnil Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gemini-code-assist ok but the existing key was already in upper case. Can you point out where it is mentioned that The gRPC Metadata.Key.of method requires header names to be all lowercase. The javadoc of io.grpc.Metadata.Key clearly states:

Valid characters in key names
Only the following ASCII characters are allowed in the names of keys:
digits: 0-9
uppercase letters: A-Z (normalized to lower)
lowercase letters: a-z
special characters: -_.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gemini-code-assist Any input on the previous comment?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are absolutely correct, jmesnil. My apologies for the oversight in my previous comment. The Javadoc for io.grpc.Metadata.Key.of indeed states that uppercase letters are normalized to lowercase, meaning that passing a header name like "A2A-Version" would be internally converted to "a2a-version" without throwing an IllegalArgumentException.

My previous statement regarding the IllegalArgumentException was inaccurate in this specific context. Thank you for pointing that out.

While gRPC handles this normalization, the HTTP/2 specification (RFC 7540, Section 8.1.2) recommends that header field names should be transmitted in lowercase. Therefore, explicitly calling .toLowerCase() as you have done in the patch for A2AHeaders.A2A_VERSION and A2AHeaders.A2A_EXTENSIONS (e.g., A2AHeaders.A2A_VERSION.toLowerCase()) is a good practice. It ensures explicit adherence to the HTTP/2 recommendation and makes the intent clear in the code, even if Metadata.Key.of provides some leniency.

So, the changes you've made to include .toLowerCase() are indeed beneficial for clarity and adherence to best practices, despite my initial incorrect assessment of the error condition.

// Extract A2A extensions header
Metadata.Key<String> extensionsKey =
Metadata.Key.of(A2AHeaders.X_A2A_EXTENSIONS, Metadata.ASCII_STRING_MARSHALLER);
Metadata.Key.of(A2AHeaders.A2A_EXTENSIONS, Metadata.ASCII_STRING_MARSHALLER);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The gRPC Metadata.Key.of method requires header names to be all lowercase. Using A2AHeaders.A2A_EXTENSIONS (which is "A2A-Extensions") will throw an IllegalArgumentException at runtime, causing the gRPC call to fail. This leads to a Denial of Service for the gRPC transport.

@jmesnil jmesnil force-pushed the A2A-Version branch 2 times, most recently from cbed2ad to 6f19b1e Compare March 6, 2026 14:40
@jmesnil
Copy link
Collaborator Author

jmesnil commented Mar 6, 2026

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the A2A header names from X-A2A-* to A2A-* to align with the latest specification. The changes are applied consistently across the client and server implementations for gRPC, JSON-RPC, and REST transports. The gRPC context keys are also updated to remove the 'x-' prefix. The refactoring in A2AExtensionsInterceptor to use static keys for metadata is a good improvement. I've found a potential issue in the client-side gRPC transport where header lookups might fail due to incorrect casing. Please see my detailed comments.

Note: Security Review is unavailable for this PR.

*/
public static final Context.Key<String> VERSION_HEADER_KEY =
Context.key("x-a2a-version");
Context.key(A2AHeaders.A2A_VERSION.toLowerCase());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yous should set a default locale here

A2A-Version and A2A-Extensions are specified by the spec (as of dec790a) instead of the X-A2A-*.

Updates the gRPC context keys to be derived from the HTTP headers and in lower case.

I did not change the `X-A2A-Notification-Token` header that is still present in the spec
but I'll open a PR to verify if that's not an omission

Signed-off-by: Jeff Mesnil <jmesnil@ibm.com>
@kabir
Copy link
Collaborator

kabir commented Mar 10, 2026

/gemini review

@kabir kabir merged commit d11484b into a2aproject:main Mar 10, 2026
11 checks passed
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The pull request renames custom HTTP headers from X-A2A-Version and X-A2A-Extensions to A2A-Version and A2A-Extensions across gRPC, JSON-RPC, and REST client and server implementations. However, the gRPC client's header extraction is fragile as it relies on lowercase keys for case-insensitive HTTP headers, potentially missing valid headers. Additionally, a security vulnerability exists in the gRPC server interceptor where metadata.get() is used for A2A-Extensions, which only retrieves the last value, unlike other implementations that use getAll(), potentially allowing an attacker to bypass security by hiding extensions.

Comment on lines +393 to 403
// Set a2a-version header if present
String versionHeader = context.getHeaders().get(A2AHeaders.A2A_VERSION.toLowerCase());
if (versionHeader != null) {
metadata.put(VERSION_KEY, versionHeader);
}

// Set X-A2A-Extensions header if present
String extensionsHeader = context.getHeaders().get(A2AHeaders.X_A2A_EXTENSIONS);
// Set a2a-extensions header if present
String extensionsHeader = context.getHeaders().get(A2AHeaders.A2A_EXTENSIONS.toLowerCase());
if (extensionsHeader != null) {
metadata.put(EXTENSIONS_KEY, extensionsHeader);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Retrieving headers from the ClientCallContext using lowercase keys is fragile. HTTP header names are case-insensitive, and it's common for users to provide them with standard casing (e.g., A2A-Version). The current implementation would fail to find such headers, leading to them being silently ignored.

To make this more robust, it's better to iterate through the headers and perform a case-insensitive comparison.

Suggested change
// Set a2a-version header if present
String versionHeader = context.getHeaders().get(A2AHeaders.A2A_VERSION.toLowerCase());
if (versionHeader != null) {
metadata.put(VERSION_KEY, versionHeader);
}
// Set X-A2A-Extensions header if present
String extensionsHeader = context.getHeaders().get(A2AHeaders.X_A2A_EXTENSIONS);
// Set a2a-extensions header if present
String extensionsHeader = context.getHeaders().get(A2AHeaders.A2A_EXTENSIONS.toLowerCase());
if (extensionsHeader != null) {
metadata.put(EXTENSIONS_KEY, extensionsHeader);
}
// Set a2a-version and a2a-extensions headers if present, ignoring case
for (Map.Entry<String, String> header : context.getHeaders().entrySet()) {
if (A2AHeaders.A2A_VERSION.equalsIgnoreCase(header.getKey())) {
metadata.put(VERSION_KEY, header.getValue());
} else if (A2AHeaders.A2A_EXTENSIONS.equalsIgnoreCase(header.getKey())) {
metadata.put(EXTENSIONS_KEY, header.getValue());
}
}

Metadata.Key<String> extensionsKey =
Metadata.Key.of(A2AHeaders.X_A2A_EXTENSIONS, Metadata.ASCII_STRING_MARSHALLER);
String extensions = metadata.get(extensionsKey);
String extensions = metadata.get(EXTENSIONS_KEY);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The gRPC interceptor uses metadata.get(EXTENSIONS_KEY) to extract the A2A-Extensions header. In gRPC-Java, metadata.get() only returns the last value if multiple headers with the same name are present. In contrast, the REST and JSON-RPC implementations correctly use getAll() to capture all values. This inconsistency allows an attacker to "hide" certain extensions from the gRPC server by appending another A2A-Extensions header to the request. If the server logic relies on the presence of an extension for security enforcement, this could lead to a bypass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants