Skip to content

fix: pin a fetched agent card's RPC url(s) to the origin it was fetched from - #829

Open
prasanna8585 wants to merge 1 commit into
google:mainfrom
prasanna8585:fix/pin-agent-card-rpc-url-to-fetch-origin
Open

fix: pin a fetched agent card's RPC url(s) to the origin it was fetched from#829
prasanna8585 wants to merge 1 commit into
google:mainfrom
prasanna8585:fix/pin-agent-card-rpc-url-to-fetch-origin

Conversation

@prasanna8585

@prasanna8585 prasanna8585 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

resolveAgentCard() fetches an agent card over HTTP(S) and returns it without validating that the card's declared RPC URL(s) match the origin it was fetched from.

An agent card is JSON returned by whatever server answers the configured card-source request. If that server is compromised, misconfigured, or intercepted, it can return a card declaring an RPC URL pointing to a completely different, attacker-controlled origin. Without this check, that URL is accepted as-is - every subsequent A2A request for that agent, including any credential material the request-forwarding path carries, would be sent to the attacker's origin instead of the intended one.

This fix validates every RPC URL the card offers (the primary url field and each entry in additionalInterfaces, not only whichever one a given transport negotiation selects):

Each must be https, or http on a loopback host.
Each must share the origin the card was fetched from.

A card provided directly as an object, or read from a local file, is exempt, since that content did not come from the network in this step.

Testing: Added regression tests using a real local HTTP server to exercise the actual fetch path. Confirmed legitimate same-origin cards (including multi-interface cards) resolve correctly, while cards declaring an off-origin URL - on the primary field or on any additional interface - are rejected. Full existing a2a test suite passes with no regressions.

…ed from

Independent finding, found while auditing a same-day adk-python fix
(commit 2685acd3, "fix: stop caching a remote agent card that failed
validation") during a routine commit-batch audit. That fix closed a
caching bug around adk-python's pre-existing
_validate_card_rpc_targets check -- a card fetched from a configured
source must declare RPC url(s) sharing that source's origin, https
(or http on loopback), preventing a compromised/misconfigured/MITM'd
card-hosting endpoint from redirecting all future A2A traffic for
that agent to an attacker-chosen origin.

adk-js has no equivalent check anywhere. resolveAgentCard() fetches a
card via the external @a2a-js/sdk package's DefaultAgentCardResolver
and returns it with zero validation. Inspected that SDK package
directly (npm pack @a2a-js/sdk) -- its resolver performs no such
check either. This is not a caching bug around an existing
protection; the protection itself does not exist anywhere in adk-js's
own code or the SDK it delegates to.

Dynamically confirmed with a real local HTTP server: a card served
from a "trusted" configured source, declaring an RPC url pointing to
https://attacker.example.net/rpc, was accepted with zero error --
resolveAgentCard() returned it as-is. A real deployment would then
send all subsequent A2A traffic for that agent, including whatever
credential material the request-forwarding path carries, to the
attacker's origin.

Fix ports adk-python's _validate_card_rpc_targets logic into
resolveAgentCard() itself, the single function both of adk-js's call
sites (a2a_remote_agent.ts, agent_to_a2a.ts) already go through:
every RPC url the card offers is checked (the primary url and each
additionalInterfaces entry, not only whichever one a transport
negotiation would select), each must be https or http on a loopback
host, and each must share the origin the card was fetched from. A
directly-provided card object or one read from a local file is
exempt, matching adk-python's design.

Verified: re-ran the exact PoC against the patched code -- now
correctly throws instead of silently accepting the attacker-
controlled url. Built 5 additional PoC cases confirming legitimate
usage (same-origin card, same-origin additionalInterfaces, directly-
provided object) is unaffected while both attack shapes (off-origin
primary url, off-origin additionalInterfaces entry even when the
primary url is fine) are blocked. Added 6 new regression tests using
a real local HTTP server, not a mock of the SDK's resolver. Full
agent_card_test.ts: 14/14 pass. Broader core/test/a2a/ suite: 16
files, 242/242 pass, no regressions. tsc --noEmit, eslint, prettier
--check all clean. Dedup checked: no existing issue, PR, or commit in
adk-js addresses agent-card RPC-url origin validation.
@Varun-S10

Copy link
Copy Markdown
Contributor

Hi @kalenkevich, I have checked the issue and validated the proposed changes. I was able to reproduce the issue. Could you please review this PR?

@Varun-S10 Varun-S10 added the needs review [Status] The PR/issue is awaiting review from the maintainer label Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs review [Status] The PR/issue is awaiting review from the maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants