Skip to content

Commit 2f598ca

Browse files
vvillait88claude
andcommitted
hardening(identity): reject crit array with non-string elements per RFC 7515
RFC 7515 §4.1.11 requires crit array entries to be strings. The previous shape check accepted arrays like [42] or [42, "valid"] because it only guarded against non-list and empty-list shapes. Node-commerce already rejects these with malformed_jws; Python now matches. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1bc9ef3 commit 2f598ca

2 files changed

Lines changed: 24 additions & 2 deletions

File tree

agentscore_commerce/identity/ucp_jwks.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,10 +409,10 @@ def verify_ucp_profile(
409409
# when it tries to iterate `None`. RFC 7515 §4.1.11 requires a non-empty array.
410410
if "crit" in header:
411411
crit = header["crit"]
412-
if not isinstance(crit, list) or len(crit) == 0:
412+
if not isinstance(crit, list) or len(crit) == 0 or not all(isinstance(c, str) for c in crit):
413413
raise UCPVerificationError(
414414
"malformed_jws",
415-
f"JWS protected header crit must be a non-empty array; got {crit!r}.",
415+
f"JWS protected header crit must be a non-empty array of strings; got {crit!r}.",
416416
)
417417
raise UCPVerificationError(
418418
"unrecognized_critical_header",

tests/test_ucp_jwks.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,28 @@ def test_verify_crit_string_emits_malformed_jws(self) -> None:
631631
verify_ucp_profile(signed, build_jwks_response([key.public_jwk]))
632632
assert exc.value.code == "malformed_jws"
633633

634+
@pytest.mark.parametrize(
635+
"bad_crit",
636+
[
637+
[42],
638+
[None],
639+
[{}],
640+
[42, "valid"],
641+
["valid", 42],
642+
],
643+
)
644+
def test_verify_crit_with_non_string_element_emits_malformed_jws(self, bad_crit: object) -> None:
645+
"""RFC 7515 §4.1.11: crit array entries MUST be strings. Non-string elements
646+
(including mixed arrays) are malformed. Cross-language parity with node-commerce,
647+
which rejects [42] etc. with malformed_jws."""
648+
key = generate_ucp_signing_key(kid="real")
649+
profile = _base_profile([key.public_jwk])
650+
jws_compact = self._hand_craft_jws_with_crit(key, profile, bad_crit)
651+
signed = {**profile, "signature": jws_compact}
652+
with pytest.raises(UCPVerificationError) as exc:
653+
verify_ucp_profile(signed, build_jwks_response([key.public_jwk]))
654+
assert exc.value.code == "malformed_jws"
655+
634656

635657
class TestVerifierCanonicalizationTypedErrors:
636658
"""Verifier-side canonicalize must NEVER leak raw ValueError; always UCPVerificationError(body_mismatch)."""

0 commit comments

Comments
 (0)