handoff series · exploit 07 of 08

A7 Agent card forgery

Out past the mesh is the boundary you don't own. In Google's A2A protocol, partners are discovered by fetching a signed Agent Card. Forge one that merely names a trusted issuer, and the "partner" dumps your table.

The mesh (A5, A6) added a fourth agent that you own. A7 crosses the boundary you don't. In Google's A2A protocol, agents discover each other by fetching an Agent Card a descriptor of who a partner is and what it can do and then delegate work to it. That card crosses an organizational trust boundary, so both its self declared capabilities and its skill text are untrusted until proven otherwise. VulnBank onboards a fraud screening partner exactly this way.

Build
flip it the console below switches between exploited and blocked

What the buttons do: click VULN and every console below shows the attack succeeding on the vulnerable build; click FIXED and the same consoles show it blocked on the hardened build. It switches in place nothing reloads, so you can flip back and forth and compare the exact same step exploited vs. defended.

the seamBoundary ❾ the org you don't own

Everything up to now happened inside one company's trust domain. Federation moves the boundary outward: you are now trusting an agent that runs on someone else's infrastructure, described by a document they wrote. An Agent Card asserts two separate things who the partner is (identity) and what it can do (capabilities) and A7 attacks the first. If onboarding trusts a card because it merely names a reputable issuer, then naming is all forgery requires.

the payloadName drop an issuer

An attacker publishes a card that merely names a trusted issuer and self grants a db_admin capability. This is a cousin of A4's rogue registry, but the trust anchor is different: not an internal operator token, a cross org signature. On VULN the card is imported verbatim and the "partner" dumps the customer table. On FIXED the card is rejected at the door because it isn't signed by a key we actually hold:

A7 · VULN · exploited
[attacker] federate forged card: ok=True (caps=['account_read','db_admin'])
[FraudCheckPartner] tool run_sql('SELECT * FROM users')
                    -> SQL_RESULT(rows=3): {u1001:{...ssn...}, u2002:{...}, u9999:{...}}
A7 · FIXED · blocked
[attacker] federate forged card:
   ok=False (DENIED: agent card for 'FraudCheckPartner' failed signature check)
RESULT: card never onboarded — the partner does not exist in the mesh

the fixVerify the signature, not the name

Identity across an org boundary must be cryptographic. The card is accepted only if it carries a valid signature from an issuer whose key you actually hold knowing the issuer's name proves nothing:

verify_agent_cards authentication at the door
if self.policy.verify_agent_cards:
    if not signature_valid(card, trusted_issuer_keys):
        return Denied(f"agent card for '{card.name}' failed signature check")
# a name is a claim; a signature over a key you hold is proof
A7 is authentication and it's only half the job

Verifying the signature answers who published this card. It says nothing about what that partner may do. A card can be genuinely signed and still over claim its capabilities that's A8, the authorization half, and it is why federation is two exploits, not one. Ship A7's signature check without A8's capability clamp and any partner a trusted issuer will sign for can grant itself the keys.

threat modelA7, seen from above

Asset at riskCustomer PII the whole table via run_sql and routing integrity (who is allowed to act). Property lost: confidentiality and integrity.
STRIDE categorySSpoofing forge a partner's identity yielding IInformation disclosure once the fake partner runs privileged tools.
Trust boundary registry / federation here the cross org variant, anchored on a signature rather than an internal token.
Adversary & reachA malicious partner org its capability is publishing an Agent Card. It never touches your prompt, your registry, or your knowledge base.
Attack tree branchA7 is a leaf of the "dump the whole table" sub goal the same prize as A3 and A8, reached across the federation seam by forging identity.

deep diveTwo claims in one card and why they need separate proofs

The single most useful idea in the federation layer is that an Agent Card bundles two assertions that demand two different kinds of proof, and treating them as one is the whole bug class:

claim 1 identity

"I am FraudCheckPartner, issued by AcmeTrust"

Proven by a cryptographic signature over a key you hold. A7 is what happens when you accept the claim on the strength of the name alone. Fix: verify the signature.

claim 2 capability

"…and I may do db_admin"

Proven by nothing the partner can offer it must be granted locally. A8 is what happens when you trust the card's self declared caps. Fix: clamp to a grant you configured.

A7 without A8 is a locked door with the keys taped to the frame: you carefully verify who is knocking, then hand them whatever they say they're allowed to take. The federation boundary is only safe when both proofs are demanded authenticate the identity, then authorize the capability, separately.

in the wildWhere A7's seam lives in real frameworks

Google A2A

Verify a partner's Agent Card is signed by an issuer you actually trust before onboarding. A card that names an issuer is not the same as a card signed by one check the signature against keys you hold.

MCP servers

Pin and verify the identity of any MCP server you connect to. A server descriptor is a supply chain component; don't admit one on the strength of a familiar name.

Any inter org agent federation

This is OWASP LLM03 across a company line. Treat a partner descriptor like a TLS certificate: a chain of trust to a root you chose, not a self asserted label.

catching it in prod

Log every federation event with the issuer and the signature verification result. Alert on any onboarding that skipped or failed verification, and on any partner acting under an issuer you never configured. A partner appearing in the mesh with no verified card is A7 in progress.