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
Open
Conversation
…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.
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? |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.