{"record":{"id":"804331c0eedb1460","repo":"mastra-ai/mastra","slug":"failed-to-canonicalize-a2a-agent-card-for-signatur","errorCode":null,"errorMessage":"Failed to canonicalize A2A Agent Card for signature verification","messagePattern":"Failed to canonicalize A2A Agent Card for signature verification","errorType":"exception","errorClass":"MastraClientError","httpStatus":200,"severity":"error","filePath":"client-sdks/client-js/src/utils/verify-agent-card-signature.ts","lineNumber":102,"sourceCode":"\n    return importSPKI(key, algorithm);\n  }\n\n  return importJWK(key as JWK, algorithm);\n}\n\nexport async function verifyAgentCardSignatureIfPresent(\n  agentCard: AgentCard,\n  options: VerifyAgentCardSignatureOptions,\n): Promise<AgentCard> {\n  const signatures = agentCard.signatures ?? [];\n  if (signatures.length === 0) {\n    return agentCard;\n  }\n\n  const canonicalPayload = canonicalize(stripAgentCardSignatures(agentCard));\n  if (!canonicalPayload) {\n    throw new MastraClientError(200, 'OK', 'Failed to canonicalize A2A Agent Card for signature verification');\n  }\n\n  const allowedAlgorithms = options.algorithms ?? [...DEFAULT_AGENT_CARD_SIGNATURE_ALGORITHMS];\n  const encodedPayload = base64url.encode(canonicalPayload);\n  const verificationErrors: string[] = [];\n\n  for (const [index, signature] of signatures.entries()) {\n    try {\n      const compactJws = `${signature.protected}.${encodedPayload}.${signature.signature}`;\n      const protectedHeader = decodeProtectedHeader(compactJws);\n\n      if (typeof protectedHeader.alg !== 'string') {\n        throw new Error('Agent Card signature is missing a protected \"alg\" header');\n      }\n\n      if (!allowedAlgorithms.includes(protectedHeader.alg)) {\n        throw new Error(`Agent Card signature algorithm \"${protectedHeader.alg}\" is not allowed`);\n      }","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/client-sdks/client-js/src/utils/verify-agent-card-signature.ts#L84-L120","documentation":"verifyAgentCardSignatureIfPresent signs verification over the canonical JSON form of the Agent Card (RFC 8785 via the `canonicalize` package). After stripping the `signatures` array, canonicalization returned a falsy value (null/empty), which means the card payload could not be deterministically serialized, so no JWS can be built that matches what the signer produced. The library throws MastraClientError(200,'OK') rather than proceeding with a payload that would never verify.","triggerScenarios":"Calling getAgentCard() on a client configured with signature verification when agentCard.signatures is non-empty and canonicalize(stripAgentCardSignatures(agentCard)) returns null/empty — i.e. the unsigned card JSON contains values RFC 8785 cannot represent (e.g. undefined fields serialized oddly, non-JSON-compatible values) or the card serializes to an empty string.","commonSituations":"A server (or proxy/MSW mock) returns an Agent Card whose fields include non-JSON-safe values or an unexpectedly empty/malformed body; a version change in @mastra/core/a2a AgentCard shape that breaks canonicalization; hand-crafted fixture cards in tests.","solutions":["Inspect the Agent Card returned by the server (log it before verification) and remove/fix any field that is not valid JSON-compatible data.","Ensure the server returns a well-formed, complete Agent Card (an empty or partial card can canonicalize to empty).","Update @mastra/core and client-js to matching versions so the AgentCard shape matches what the signer canonicalized.","If the card is genuinely unsigned, strip the signatures array server-side so the verification path is skipped entirely."],"exampleFix":"// before: verifying a mocked card fixture with extra non-JSON field\ncard.customData = undefined;\nawait getAgentCard();\n\n// after: keep the card strictly JSON-serializable\ndelete card.customData;\nawait getAgentCard();","handlingStrategy":"validation","validationCode":"// before calling the API, ensure the card you expect is JSON-canonicalizable\nconst res = await fetch(`${baseUrl}/.well-known/agent-card.json`);\nconst card = await res.json();\nconst stripped = { ...card };\ndelete stripped.signatures;\nif (!JSON.stringify(stripped) || stripped === undefined) {\n  throw new Error('Agent Card is not JSON-serializable; verification would fail');\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep Agent Card fixtures strictly JSON-serializable (no undefined, functions, or cycles).","Validate server responses against the AgentCard schema before enabling signature verification.","Pin matching @mastra/core and client-js versions.","Add a smoke test that runs canonicalize() over the real served card in CI."],"tags":["a2a","agent-card","json-canonicalization","signature-verification"],"backgroundTag":"json-canonicalization-failed","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}