mastra-ai/mastra · error · MastraClientError

Failed to canonicalize A2A Agent Card for signature verifica

Error message

Failed to canonicalize A2A Agent Card for signature verification

What it means

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.

Source

Thrown at client-sdks/client-js/src/utils/verify-agent-card-signature.ts:102

    return importSPKI(key, algorithm);
  }

  return importJWK(key as JWK, algorithm);
}

export async function verifyAgentCardSignatureIfPresent(
  agentCard: AgentCard,
  options: VerifyAgentCardSignatureOptions,
): Promise<AgentCard> {
  const signatures = agentCard.signatures ?? [];
  if (signatures.length === 0) {
    return agentCard;
  }

  const canonicalPayload = canonicalize(stripAgentCardSignatures(agentCard));
  if (!canonicalPayload) {
    throw new MastraClientError(200, 'OK', 'Failed to canonicalize A2A Agent Card for signature verification');
  }

  const allowedAlgorithms = options.algorithms ?? [...DEFAULT_AGENT_CARD_SIGNATURE_ALGORITHMS];
  const encodedPayload = base64url.encode(canonicalPayload);
  const verificationErrors: string[] = [];

  for (const [index, signature] of signatures.entries()) {
    try {
      const compactJws = `${signature.protected}.${encodedPayload}.${signature.signature}`;
      const protectedHeader = decodeProtectedHeader(compactJws);

      if (typeof protectedHeader.alg !== 'string') {
        throw new Error('Agent Card signature is missing a protected "alg" header');
      }

      if (!allowedAlgorithms.includes(protectedHeader.alg)) {
        throw new Error(`Agent Card signature algorithm "${protectedHeader.alg}" is not allowed`);
      }

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Inspect the Agent Card returned by the server (log it before verification) and remove/fix any field that is not valid JSON-compatible data.
  2. Ensure the server returns a well-formed, complete Agent Card (an empty or partial card can canonicalize to empty).
  3. Update @mastra/core and client-js to matching versions so the AgentCard shape matches what the signer canonicalized.
  4. If the card is genuinely unsigned, strip the signatures array server-side so the verification path is skipped entirely.

Example fix

// before: verifying a mocked card fixture with extra non-JSON field
card.customData = undefined;
await getAgentCard();

// after: keep the card strictly JSON-serializable
delete card.customData;
await getAgentCard();
Defensive patterns

Strategy: validation

Validate before calling

// before calling the API, ensure the card you expect is JSON-canonicalizable
const res = await fetch(`${baseUrl}/.well-known/agent-card.json`);
const card = await res.json();
const stripped = { ...card };
delete stripped.signatures;
if (!JSON.stringify(stripped) || stripped === undefined) {
  throw new Error('Agent Card is not JSON-serializable; verification would fail');
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/804331c0eedb1460. Report an issue: GitHub.