Skip to content

feat(tr-env): add TR-ENV-005, cnf.jwk must carry no private key material - #98

Merged
imran-siddique merged 1 commit into
mainfrom
sec/cnf-jwk-no-private-material
Sep 6, 2026
Merged

feat(tr-env): add TR-ENV-005, cnf.jwk must carry no private key material#98
imran-siddique merged 1 commit into
mainfrom
sec/cnf-jwk-no-private-material

Conversation

@imran-siddique

Copy link
Copy Markdown
Member

The trace-tests half of GHSA-vc4p-h84j-7qxj. Pairs with agentrust-io/trace-spec#296, which fixes the two schema copies in that repo.

Two gaps here

The schema copy. schemas/trace-claim.json is a third copy of the Trust Record schema and had the same hole: the cnf.jwk block constrained only the kty/crv/x/y shapes, and everything else fell through additionalProperties. A record carrying its own private key validated.

The suite itself. TR-ENV-004 checks that cnf.jwk.kty is present. A record carrying d satisfies that, so it passed the whole conformance suite.

That second one is the part worth fixing carefully. A conformance suite that passes a record which publishes its own signing key is worse than having no check, because the pass is what a producer will cite.

Why RFC 8747 makes this a real finding

cnf is a confirmation key: the public half, present so a verifier can bind the record to the key that signed it. There is no attacker step involved, and that is not a mitigation. A Trust Record is signed, self-authenticating and typically anchored, so once it is out the key is out and the identity has to be revoked.

TR-ENV-005

Added as a distinct code rather than folded into 004. Presence of kty and absence of private material are different requirements, and someone reading a conformance report should be able to tell which one failed. The finding names every member it found, because a producer needs to know exactly what leaked.

Covers the seven members d, p, q, dp, dq, qi, k — the same set as _JWK_PRIVATE_PARAMS in the agentrust-trace reference model, which has always refused them on the producer side.

Documented in docs/error-codes.md and docs/modules/tr-env.md.

Tests

Ten in test_tr_env.py: one per private member, one that the message names every member found, and one that an ordinary public JWK still passes. One of them asserts that TR-ENV-004 still passes on a leaking record, which is the whole reason 005 had to exist as its own code.

Reverting tr_env.py turns all eight private-material tests red.

Full suite: 553 passed, 5 xpassed.

Note on the local environment

Collection fails in this repo's .venv with ModuleNotFoundError: No module named 'rfc8785' on a clean checkout of main, before any change here. Installing it locally gives the run above. Worth a look separately — CI installs from the pin, so this is a local venv drift rather than a repo problem.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XbDBXDWWvMFa7c2jGgyq9t

GHSA-vc4p-h84j-7qxj, the trace-tests half.

This repo carries a third copy of the Trust Record schema with the same
gap trace-spec had: nothing constrained private JWK members, so a record
publishing the key that signed it validated. The conformance suite did
not catch it either. TR-ENV-004 checks that cnf.jwk.kty is present, and a
record carrying `d` satisfies that, so it passed the whole suite.

A conformance suite that passes a record which leaks its own signing key
is worse than no check, because the pass is what a producer will cite.

Adds the schema constraint and TR-ENV-005 as a distinct code rather than
folding it into 004: presence of kty and absence of private material are
different requirements, and a consumer reading a report should be able to
tell which one failed. The finding names every member it found, since a
producer needs to know what leaked.

RFC 8747 makes cnf a confirmation key, the public half, present so a
verifier can bind the record to the key that signed it. A record is
signed, self-authenticating and usually anchored, so a key exposed this
way must be treated as compromised.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XbDBXDWWvMFa7c2jGgyq9t
@imran-siddique
imran-siddique requested a review from a team as a code owner September 6, 2026 00:04
@imran-siddique
imran-siddique merged commit 7a43155 into main Sep 6, 2026
7 checks passed
@imran-siddique
imran-siddique deleted the sec/cnf-jwk-no-private-material branch September 6, 2026 00:12
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.

1 participant