Skip to content

fix(provenance,conformance): reject private JWK members and wrap _pubkey_from_jwk errors (#535) - #541

Open
drk-ram wants to merge 1 commit into
agentrust-io:mainfrom
drk-ram:drk-ram-fix-provenance-and-tr-sig-cnf-jwk-535
Open

drk-ram wants to merge 1 commit into
agentrust-io:mainfrom
drk-ram:drk-ram-fix-provenance-and-tr-sig-cnf-jwk-535

Conversation

@drk-ram

@drk-ram drk-ram commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

What this changes

Closes #535.

Fixes three gaps around RFC 7517 / RFC 7518 private key material ("d", "p", "q", "dp", "dq", "qi", "k", GHSA-vc4p-h84j-7qxj) and JWK error handling across provenance.verify_record and conformance/src/trace_tests/modules/tr_sig.py:

  1. src/agentrust_trace/provenance.py (verify_record):
    • Rejects any record["cnf"]["jwk"] or trusted_jwk carrying private/secret JWK members (_JWK_PRIVATE_PARAMS = frozenset({"d", "p", "q", "dp", "dq", "qi", "k"})) with ProvenanceError, naming only the offending member key names and never echoing secret values.
    • Checks trusted_jwk for private key material before consulting revocation so a call with revocation supplied does not invoke the revocation store or surface a thumbprint/revocation error before rejecting a trusted_jwk carrying private key material.
    • Wraps _pubkey_from_jwk(trusted_jwk) in try ... except ValueError as exc: raise ProvenanceError(f"the record's embedded key is unusable: {exc}") from exc so non-Ed25519 or malformed JWKs (RSA, EC, X25519, short x, or padded/non-canonical base64url x) raise ProvenanceError instead of leaking a bare ValueError.
  2. conformance/src/trace_tests/modules/tr_sig.py (check and check_cmcp_runtime):
    • Defines _JWK_PRIVATE_PARAMS = frozenset({"d", "p", "q", "dp", "dq", "qi", "k"}) (matching tr_env.py and agentrust_trace.models).
    • In check_cmcp_runtime, rejects any trace.cnf.jwk carrying a member of _JWK_PRIVATE_PARAMS with Finding("TR-SIG-001", Status.FAIL, ...) before signature verification, without echoing the secret value.
    • In check (plain trace format), checks _JWK_PRIVATE_PARAMS.intersection(jwk) instead of only "d" in jwk, emitting TR-SIG-004 (Status.FAIL) and TR-SIG-005 (Status.UNVERIFIED) for all seven private JWK parameters without echoing the secret value.

Cold-reader review (code-review sub-agent):

  1. Finding 1 (src/agentrust_trace/provenance.py): The initial placement of the trusted_jwk private-key check ran after if revocation is not None: _check_not_revoked(trusted_jwk, revocation). When revocation was passed alongside a trusted_jwk containing private key material, _check_not_revoked ran first (invoking the external revocation store, or failing inside jwk_thumbprint for kty="oct").
    • Changed: Moved the trusted_jwk private-key check ahead of if revocation is not None: and added an assertion in test_provenance_verify_record_refuses_private_key_material_in_trusted_jwk verifying that a revocation store callback is never invoked when trusted_jwk carries private key material.
  2. Finding 2 (conformance/tests/unit/test_tr_sig.py): test_plain_trace_all_private_jwk_members_fail_tr_sig_004 initially used an unsigned EC record at level=0; testing a validly signed OKP/Ed25519 plain trace record at level=1 directly pins that a validly signed record carrying any of the 7 private JWK members is never verified as TR-SIG-005 PASS.
    • Changed: Updated test_plain_trace_all_private_jwk_members_fail_tr_sig_004 to sign a valid OKP/Ed25519 plain trace record carrying each of the 7 private JWK members and check at level=1.

Contributor: Raghuvar Durga (@drk-ram)

Type of change

  • Editorial (typo, link fix, clarification: no normative effect)
  • Non-breaking spec change (new optional field, new platform profile, informative addition)
  • Breaking spec change (requires 14-day comment period and Project Lead sign-off)
  • Schema change
  • Example addition

Spec section

  • spec/server-provenance-v1.md / spec/server-provenance-v2.md reference implementation (src/agentrust_trace/provenance.py)
  • spec/trace-v0.2.md section 3.2.1 conformance module (conformance/src/trace_tests/modules/tr_sig.py)

Tests for a fix

Verified with python tools/review_bar.py --base origin/main --head HEAD (19 fail on behaviour on origin/main, 0 fail on a missing name or file):

  • tests/test_provenance_cnf_boundary.py::test_provenance_verify_record_refuses_private_key_material_in_cnf_jwk ([d], [p], [q], [dp], [dq], [qi], [k]): fails on origin/main with DID NOT RAISE ProvenanceError.
  • tests/test_provenance_cnf_boundary.py::test_provenance_verify_record_refuses_private_key_material_in_trusted_jwk ([d], [p], [q], [dp], [dq], [qi], [k]): fails on origin/main with DID NOT RAISE ProvenanceError.
  • tests/test_provenance_cnf_boundary.py::test_provenance_verify_record_wraps_pubkey_from_jwk_errors_in_provenance_error ([rsa], [ec], [x25519], [short-x], [padded-x]): fails on origin/main with unhandled ValueError.
  • conformance/tests/unit/test_tr_sig.py::test_cmcp_runtime_private_key_material_in_cnf_jwk_fails ([d], [p], [q], [dp], [dq], [qi], [k]): fails when the check_cmcp_runtime private-key check is switched off (Status.PASS instead of Status.FAIL).
  • conformance/tests/unit/test_tr_sig.py::test_plain_trace_all_private_jwk_members_fail_tr_sig_004 ([p], [q], [dp], [dq], [qi], [k]): fails when tr_sig.check only inspects "d" in jwk.

Validation commands run and results:

  • pytest tests/test_provenance_cnf_boundary.py tests/test_provenance.py tests/test_public_functions_raise_what_they_document.py -q: passed (433 passed)
  • pytest conformance/tests -q: passed (792 passed, 5 xpassed)
  • python conformance/measurement/scripts/mutate_modules.py conformance: passed (27 checks measured, 27 verified, 0 unverified)
  • python tools/review_bar.py --base origin/main --head HEAD: passed (19 fail on behaviour on base, 0 fail on missing name)
  • ruff check src tests scripts: passed
  • mypy src/agentrust_trace: passed
  • python tools/check_dashes.py: passed

Checklist

  • DCO sign-off on all commits (git commit -s)
  • CHANGELOG.md updated (for any normative change)
  • Breaking changes marked with <!-- CHANGED: #NNN: description --> in spec text
  • Backward compatibility statement included (for breaking changes)

…key_from_jwk errors

Reject RFC 7517/7518 private/secret JWK members (d, p, q, dp, dq, qi, k) in provenance.verify_record (both record['cnf']['jwk'] and trusted_jwk) and in conformance tr_sig (both check and check_cmcp_runtime), without echoing secret values in error or finding messages. Also wrap _pubkey_from_jwk(trusted_jwk) in provenance.verify_record so non-Ed25519 or malformed JWKs raise ProvenanceError instead of leaking a bare ValueError.

Closes agentrust-io#535

Signed-off-by: Raghuvar Durga <177662415+drk-ram@users.noreply.github.com>

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@drk-ram
drk-ram requested review from a team, lywinged and rajnisht7 as code owners October 15, 2026 22:46

@rajnisht7 rajnisht7 left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This PR closes the three gaps from #535 cleanly. provenance.verify_record now rejects all seven private JWK members on both cnf.jwk and trusted_jwk, does that check before any revocation callback, and wraps _pubkey_from_jwk failures as ProvenanceError instead of leaking a bare ValueError. Conformance tr_sig matches: check_cmcp_runtime rejects the full private set early, and plain check no longer keys only on "d" it uses the same _JWK_PRIVATE_PARAMS intersection. The set matches models/tr_env/schema, messages name only member keys (tests assert the secret is never echoed), and the new tests cover all seven members on both surfaces, including the signed level-1 plain-trace case and the revocation-not-called assertion.

One small nit: when _pubkey_from_jwk(trusted_jwk) fails, the message still says “the record's embedded key is unusable”; something like “trusted_jwk is unusable” would be more accurate, but it is not a functional issue

This branch has not been deployed

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

Labels

None yet

Projects

None yet

2 participants