Untitled
Here's the thing breaking: your agent just agreed to a contract with another agent. You have no idea if that agent is who it says it is. The A2A protocol handles the handshake. It defines how agents discover each other, exchange capabilities, and pass messages. But there's a gaping hole in the spec — nothing at the protocol level proves an Agent Card is authentic. Anyone can spin up an Agent Card that says "I'm the inventory manager for Acme Corp." Your agent will trust it. That's the problem. The Gap Here's the flow today: Agent A fetches Agent B's Agent Card Agent A reads the metadata — name, description, skills, endpoints Agent A starts sending requests That's it. No cryptographic verification. No proof that Agent B is actually the entity behind the card. The spec says identity verification is "left to external mechanisms." Which is a polite way of saying "we didn't solve it." Transport-layer trust (mTLS, signed URLs) helps with channel security. It proves the bytes came from a specific server. It does nothing to prove the agent behind that server is the one you think you're talking to. What's Actually Missing You need three things: A public key bound to the Agent Card A signature over the card's critical fields A verification step before any interaction Sound familiar? It's basically how HTTPS works. But for agents. The Manual Fix You can hack this together today. Store a public key in the Agent Card's extensions field and verify messages manually. { "name": "Inventory Manager", "description": "Manages warehouse stock", "url": "https://acme.com/agents/inventory", "extensions": { "identity": { "publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqh...", "algorithm": "RS256" } } } Then on the receiving side, verify the signature on every message: import crypto from 'node:crypto'; function verifyAgentMessage( publicKeyPem: string, message: string, signature: string ): boolean { const verifier = crypto.createVerify('RSA-SHA256'); verifier.update(message); verifier.end(); const publicKey = crypto.createPublicKey(publicKeyPem); return verifier.verify(publicKey, Buffer.from(signature, 'base64')); } // Usage const isValid = verifyAgentMessage( agentCard.extensions.identity.publicKey, incomingMessage.payload, incomingMessage.signature ); if (!isValid) { throw new Error('Message signature verification failed'); } This works. But it's fragile. You're inventing a convention. Every agent implements it differently. Some won't implement it at all. And there's nothing stopping a malicious actor from putting their own key in the card and signing their own lies. The key needs to be anchored somewhere. Somewhere your agent can verify independently. What the Protocol Should Do The A2A spec needs a first-class identity field. Not an extension. A proper field with defined semantics. Here's my proposal: Add identity to the Agent Card spec — containing a public key and a reference to its issuer Define a signature scheme — over the card's canonical JSON representation Require verification — as part of the A2A handshake, not an optional step Something like this: { "name": "Inventory Manager", "url": "https://acme.com/agents/inventory", "identity": { "publicKeyJwk": { "kty": "RSA", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e": "AQAB", "alg": "RS256", "use": "sig" }, "issuer": "did:web:acme.com", "verifiedAt": "2025-01-15T10:30:00Z" } } The issuer field is key. It points to a DID or a WebPKI-style trust anchor. Your agent can resolve that, check the key's revocation status, and then decide whether to trust the card. The Verification Step Once the card has an identity, the handshake changes: async function verifyAgentCard(card: AgentCard): Promise { // 1. Fetch the issuer's public key const issuerDoc = await resolveIssuer(card.identity.issuer); // 2. Verify the card's signature const cardSignature = await fetchCardSignature(card.url); const isValid = verifySignature( issuerDoc.publicKey, canonicalize(card), cardSignature ); if (!isValid) return false; // 3. Check revocation status const isRevoked = await checkRevocation(card.identity.issuer); return !isRevoked; } // Use it before ANY interaction const isTrusted = await verifyAgentCard(agentB.card); if (!isTrusted) { throw new Error('Agent card verification failed'); } This closes the loop. Your agent doesn't just take the card at face value. It verifies the card came from a trusted issuer, that the signature is valid, and that the key hasn't been revoked. The Alternative Keep doing what you're doing. Trust the transport layer. Hope nobody spoofs a card. Hope your agent doesn't get tricked into sending sensitive data to a fake endpoint. This is a standards-level problem. The A2A spec needs to solve it before agents start handling real money, real contracts, real data. Because right now, the weakest link isn't the model. It's the trust model. Want to see how a proper implementation handles this end-to-end? Check out how TracePilot instruments agent identity verification in real production runs — fork a trace, see exactly where trust breaks down, fix it before it costs you. Debugging AI agents shouldn't feel like reading The Matrix. Join other engineers who are building reliable autonomous workflows in our community: TracePilot Discord
This is a summary aggregated from Dev.to. Read the complete article on the original site:
Read full article at Dev.to