From 6bd1b3d083cd1f673fb01cefe354092a6af43fbd Mon Sep 17 00:00:00 2001 From: "Altru.dev" Date: Mon, 31 Aug 2026 20:09:57 -0700 Subject: [PATCH 1/2] fix(provenance): require cnf.jwk during verification (#255) Signed-off-by: altrudev <266135212+altrudev@users.noreply.github.com> --- src/agentrust_trace/provenance.py | 34 ++++++++++++++------------- tests/test_provenance_cnf_boundary.py | 7 +++--- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/agentrust_trace/provenance.py b/src/agentrust_trace/provenance.py index 9d99adf9..bfaa8235 100644 --- a/src/agentrust_trace/provenance.py +++ b/src/agentrust_trace/provenance.py @@ -378,23 +378,25 @@ def verify_record( cnf = _as_object(record.get("cnf"), "cnf") embedded = cnf.get("jwk") - if embedded: - # Compared by RFC 7638 thumbprint, not by dict equality. A JWK is identified by - # its key material; `kid`, `use` and `alg` are optional members that carry none - # of it, and a key resolved from a JWKS endpoint normally has `kid` while - # `key_to_jwk` emits the bare minimum. Dict equality made that difference fatal - # and rejected records signed by exactly the right key. - from hmac import compare_digest + if not embedded: + raise ProvenanceError("record carries no cnf.jwk") - try: - matched = compare_digest(jwk_thumbprint(embedded), jwk_thumbprint(trusted_jwk)) - except ValueError as exc: - raise ProvenanceError(f"the record's embedded key is unusable: {exc}") from exc - if not matched: - raise ProvenanceError( - "the record's embedded key is not the trusted key. A record signed by " - "some other key is a record about a server somebody else is describing." - ) + # Compared by RFC 7638 thumbprint, not by dict equality. A JWK is identified by + # its key material; `kid`, `use` and `alg` are optional members that carry none + # of it, and a key resolved from a JWKS endpoint normally has `kid` while + # `key_to_jwk` emits the bare minimum. Dict equality made that difference fatal + # and rejected records signed by exactly the right key. + from hmac import compare_digest + + try: + matched = compare_digest(jwk_thumbprint(embedded), jwk_thumbprint(trusted_jwk)) + except ValueError as exc: + raise ProvenanceError(f"the record's embedded key is unusable: {exc}") from exc + if not matched: + raise ProvenanceError( + "the record's embedded key is not the trusted key. A record signed by " + "some other key is a record about a server somebody else is describing." + ) pub = _pubkey_from_jwk(trusted_jwk) body = _canonical_bytes({k: v for k, v in record.items() if k != "signature"}) diff --git a/tests/test_provenance_cnf_boundary.py b/tests/test_provenance_cnf_boundary.py index c4b9ffe0..8dc44cfc 100644 --- a/tests/test_provenance_cnf_boundary.py +++ b/tests/test_provenance_cnf_boundary.py @@ -55,10 +55,11 @@ def test_non_object_cnf_is_refused_through_provenance_error(bad_cnf) -> None: verify_record(record, trusted) -@pytest.mark.parametrize("cnf", [_MISSING, None, {}]) -def test_missing_null_and_empty_object_cnf_preserve_existing_behavior(cnf) -> None: +@pytest.mark.parametrize("cnf", [_MISSING, None, {}, {"jwk": None}]) +def test_missing_or_empty_cnf_jwk_is_refused(cnf) -> None: record, trusted = _signed_with_cnf(cnf) - verify_record(record, trusted) + with pytest.raises(ProvenanceError, match="no cnf.jwk"): + verify_record(record, trusted) def test_valid_embedded_cnf_jwk_still_verifies() -> None: From 62add138cf29de78fb6d35ec383c45972f64ae0d Mon Sep 17 00:00:00 2001 From: "Altru.dev" Date: Mon, 31 Aug 2026 20:10:06 -0700 Subject: [PATCH 2/2] test(provenance): pin falsey embedded jwk guard --- tests/test_provenance_cnf_boundary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_provenance_cnf_boundary.py b/tests/test_provenance_cnf_boundary.py index 8dc44cfc..86a441ed 100644 --- a/tests/test_provenance_cnf_boundary.py +++ b/tests/test_provenance_cnf_boundary.py @@ -55,7 +55,7 @@ def test_non_object_cnf_is_refused_through_provenance_error(bad_cnf) -> None: verify_record(record, trusted) -@pytest.mark.parametrize("cnf", [_MISSING, None, {}, {"jwk": None}]) +@pytest.mark.parametrize("cnf", [_MISSING, None, {}, {"jwk": None}, {"jwk": {}}]) def test_missing_or_empty_cnf_jwk_is_refused(cnf) -> None: record, trusted = _signed_with_cnf(cnf) with pytest.raises(ProvenanceError, match="no cnf.jwk"):