Skip to content

feat(agent_card): add CARD-SIGN-003 protected header structural tests#196

Open
kuangmi-bit wants to merge 1 commit into
a2aproject:mainfrom
kuangmi-bit:card-sign-003-protected-header
Open

feat(agent_card): add CARD-SIGN-003 protected header structural tests#196
kuangmi-bit wants to merge 1 commit into
a2aproject:mainfrom
kuangmi-bit:card-sign-003-protected-header

Conversation

@kuangmi-bit

Copy link
Copy Markdown

What

Adds reference vectors and structural tests for CARD-SIGN-003 (§8.4.2): the JWS protected header MUST include alg (algorithm) and kid (key ID) parameters.

Changes

  • card_signing_protected_header_vectors.json: 6 test cases

    • cardsign-hdr-001: valid ES256 header
    • cardsign-hdr-002: valid RS256 header
    • cardsign-hdr-003: missing kid → negative case
    • cardsign-hdr-004: missing alg → negative case
    • cardsign-hdr-005: empty alg → negative case
    • cardsign-hdr-006: multi-signature card (ES384 + EdDSA)
  • test_card_signing_protected_header.py: two parametrized tests

    • test_protected_header_has_alg_and_kid (MUST): base64url-decodes each signature's protected field and validates alg + kid are present, non-empty strings
    • test_protected_header_alg_is_known_jws_algorithm (SHOULD): advisory check against RFC 7518 §3.1 standard algorithm names
  • tck/requirements/agent_card.py: Remove NOT_AUTOMATABLE from CARD-SIGN-003

Design decisions

  • No SUT required. These are offline structural tests operating on static vectors — the same reference-vector pattern as PR tests(agent_card): reference canonicalization vectors for CARD-SIGN-001/002 (JCS signing payload) #194. No transport, no live agent needed.
  • No new dependencies. Uses only stdlib base64 + json. No jwcrypto or rfc8785.
  • Negative cases included. The vectors explicitly test missing kid, missing alg, and empty alg to prove the validator catches violations.
  • SHOULD-level algorithm check separated from MUST. The known-algorithm test (_KNOWN_JWS_ALGORITHMS from RFC 7518) is a separate test so a non-standard alg produces a warning-level failure rather than blocking the MUST check.

Test results

12 passed in 0.01s

make lint clean. uv run pytest green.

Relationship to other PRs

This fixes #187

…ation

Add reference vectors and structural tests for CARD-SIGN-003 (§8.4.2):
JWS protected header MUST include alg and kid parameters.

- card_signing_protected_header_vectors.json: 6 test cases covering
  valid headers (ES256/RS256/EdDSA/ES384), missing kid, missing alg,
  empty alg, and multi-signature cards
- test_card_signing_protected_header.py: two parametrized tests
  - test_protected_header_has_alg_and_kid (MUST): base64url-decodes
    protected header and validates alg + kid presence
  - test_protected_header_alg_is_known_jws_algorithm (SHOULD):
    advisory check against RFC 7518 §3.1 algorithm names
- Remove NOT_AUTOMATABLE tag from CARD-SIGN-003 since these are
  offline structural checks requiring no SUT

12/12 tests pass, lint clean. No new dependencies (base64 + json
are stdlib).

This fixes a2aproject#187

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request automates the validation of the CARD-SIGN-003 requirement by removing the NOT_AUTOMATABLE tag and introducing test vectors and offline tests to verify that JWS protected headers contain valid alg and kid parameters. The review feedback points out potential AttributeError crashes in the test suite if a malformed JWS protected header decodes to a non-dictionary JSON type, and suggests adding explicit type checks in both _validate_protected_header and test_protected_header_alg_is_known_jws_algorithm to handle these cases gracefully.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +64 to +73
def _validate_protected_header(
case_id: str, header: dict
) -> list[str]:
"""Validate that a decoded JWS protected header satisfies CARD-SIGN-003.

Returns a list of error messages (empty list = valid).
"""
errors: list[str] = []

alg = header.get("alg")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The _validate_protected_header function assumes that the decoded JSON header is always a dictionary. However, json.loads can return other valid JSON types (such as a list, string, number, boolean, or null) if the JWS protected header is malformed. If header is not a dictionary, calling header.get() will raise an AttributeError and crash the test suite. Adding an explicit type check ensures the test fails gracefully with a clear validation error.

Suggested change
def _validate_protected_header(
case_id: str, header: dict
) -> list[str]:
"""Validate that a decoded JWS protected header satisfies CARD-SIGN-003.
Returns a list of error messages (empty list = valid).
"""
errors: list[str] = []
alg = header.get("alg")
def _validate_protected_header(
case_id: str, header: object
) -> list[str]:
"""Validate that a decoded JWS protected header satisfies CARD-SIGN-003.
Returns a list of error messages (empty list = valid).
"""
errors: list[str] = []
if not isinstance(header, dict):
errors.append(f"{case_id}: protected header is not a JSON object")
return errors
alg = header.get("alg")

Comment on lines +146 to +147
alg = header.get("alg", "")
if alg and alg not in _KNOWN_JWS_ALGORITHMS:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In test_protected_header_alg_is_known_jws_algorithm, if the decoded header is not a dictionary (e.g., a list or string), calling header.get("alg", "") will raise an AttributeError outside of the try...except block. Adding a check to skip non-dictionary headers ensures the test behaves robustly, as structural non-dictionary errors are already handled by test_protected_header_has_alg_and_kid.

        if not isinstance(header, dict):
            continue

        alg = header.get("alg", "")
        if alg and alg not in _KNOWN_JWS_ALGORITHMS:

@chopmob-cloud

Copy link
Copy Markdown

Reviewed and independently validated the vectors. I decoded each of the 6 cases' signatures[].protected (base64url, padding restored), applied the alg-and-kid presence rule, and every expected_valid reproduces: 001 (ES256 / key-1) and 002 (RS256 / 2025-04-01) pass, 003 (missing kid), 004 (missing alg), and 005 (empty alg) fail with the stated expected_error, and 006 (multi-sig, ES384 / v2-key) passes. The decoded alg / kid on the positives match the vector values byte for byte.

Two things here are right and worth keeping:

  1. Presence, not an allowlist. The positives span ES256, RS256, and ES384, so the test asserts alg and kid are present and non-empty per section 8.4.2 without pinning a closed algorithm set. That matches the scope agreed on Implement CARD-SIGN-* agent card signing conformance tests (TASK-29) #187: CARD-SIGN-003 is structural presence, and algorithm policy stays a separate concern.
  2. Multi-sig is handled. 006 requires every signature's header to carry alg and kid, not just the first.

One suggestion to complete the decode contract: add a negative where protected does not decode to a JSON object at all (malformed base64url, or valid base64url that is not a JSON object). Section 8.4.2 requires the header to decode to JSON containing alg and kid, so a non-decodable header is a distinct failure from a decodable-but-missing-field one, and a case for it also confirms _decode_protected_header surfaces that as a validation failure rather than an uncaught exception.

With #194 covering the 001/002 canonicalization payload and this covering 003 structural validation, the three automatable CARD-SIGN requirements are in good shape. Nice work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement CARD-SIGN-* agent card signing conformance tests (TASK-29)

2 participants