Skip to content

fix(tr-sig): stop publishing a finding whose message names a different code - #76

Merged
imran-siddique merged 1 commit into
agentrust-io:mainfrom
lywinged:fix/tr-sig-message-code-mismatch
Aug 23, 2026
Merged

fix(tr-sig): stop publishing a finding whose message names a different code#76
imran-siddique merged 1 commit into
agentrust-io:mainfrom
lywinged:fix/tr-sig-message-code-mismatch

Conversation

@lywinged

Copy link
Copy Markdown
Collaborator

Stacked. Merge #74, then #75, then this one. Each branch contains the commits below it, so until they land the diff here shows all three. GitHub re-scopes it as each merges. Merging out of order is not harmful, it just pulls the earlier commits in with this one and closes the earlier PR with an empty diff.

report.py publishes both halves: the JSON artifact carries code per finding and the HTML table prints it beside the text. They disagreed.

_verify_ed25519 returned messages prefixed with TR-SIG-001, TR-SIG-002 and TR-SIG-003, and two callers attached those messages to findings of their own: check under TR-SIG-005, check_cmcp_runtime under TR-SIG-001. A code written in that helper could only ever match one of the two, and matched neither.

Measured across every module over a mutation sweep, four distinct instances, all in tr_sig:

code=TR-SIG-005  message names TR-SIG-001: signature verification failed
code=TR-SIG-005  message names TR-SIG-002: invalid public key in cnf.jwk.x
code=TR-SIG-005  message names TR-SIG-003: invalid base64url signature
code=TR-SIG-001  message names TR-SIG-002: invalid public key in cnf.jwk.x

So a malformed signature was forwarded as a TR-SIG-005 finding reading "TR-SIG-003" — a code the suite had not used and the docs described as something else.

The fix

The helper's messages name no code. Nothing else about them changes: the reason for the failure is still there, and Finding.code becomes the only place a code appears. Naming a module's own code in its message is the convention everywhere else here and is left alone.

What that exposed

TR-SIG-003 had no other user. It was never a Finding.code anywhere; the message prefix was the whole of its existence. Removing it leaves a code documented in three pages and emitted by nothing, so the rows are removed. The condition they described, a signature that does not verify, is reported by TR-SIG-005 for a plain record and TR-SIG-001 for a cmcp claim, both documented.

The two rows beside it were also wrong. TR-SIG-001 was documented as "Signature algorithm is not Ed25519", which nothing reports under that code: in check_cmcp_runtime it means a missing signature, or the verification outcome itself. TR-SIG-002 was "cnf.jwk missing or malformed"; it means a key that is not OKP/Ed25519 or a missing x, and only in the cmcp path. Both rows now say so, and the Level 0 failure list and the tutorial's reference table follow, since both cited cmcp-only codes for plain records.

The guard

tests/test_findings_are_self_consistent.py asserts that no finding's message names a code other than its own, over both entry points and a mutation set aimed at the error branches rather than at breadth. Verified by restoring each of the three prefixes on its own: each fails the guard, in both the plain and the cmcp case.

It also counts the findings it looked at and fails on none. Its first version did not: every record is skipped when a module raises, so a module that raised on all of them reported a pass over nothing — measured by making check raise before its first finding. That is the same degradation the sample guard on the error-code branch was hardened against, reproduced one branch later.

The other guard needed strengthening to see any of this

The docs guard from the error-code branch matched codes by regex over module file text. The docstring added here, explaining why TR-SIG-003 must not appear in a message, was on its own enough to keep the deleted code looking alive: with the row removed and no emitter left, that guard still passed.

It now reads string literals through ast, skipping module, class and function docstrings; comments are dropped by the parse. Verified both ways: re-adding the dead row fails, and documenting a code no module emits fails. On a probe carrying a code in a module docstring, a function docstring, a comment and a real literal, only the literal is seen.

Removing the prefix also settles that guard's own rationale, which said the named set and the Finding.code set differ and gave TR-SIG-003 as the example. Measured after this change they are identical, so the docstring now says that, and gives the reason the match stays on literals: it reads source text and cannot tell which literal reaches a Finding.

Unrelated, found while checking where codes appear

docs/quickstart.md documented an output format the CLI does not produce. Its sample put the error code in the first column, where the CLI prints the module, and none of its three messages exists anywhere in the source:

TR-SIG-001  PASS  Signature: Ed25519 algorithm confirmed

Replaced with output from a real run, and the surrounding text now says where a code does and does not appear.


Merge order

  1. docs: align the published error codes and record samples with the modules #74docs: align the published error codes and record samples with the modules
  2. fix(tr-sig): report a malformed record instead of raising on it #75fix(tr-sig): report a malformed record instead of raising on it
  3. this one

#74 corrects the TR-SIG rows; #75 adds the private-key condition back to TR-SIG-004; this one removes TR-SIG-003 and reclassifies TR-SIG-001 and TR-SIG-002 as cmcp-only. Reading them in that order is also the order they make sense in.


225 passed, 5 xpassed. The two test files this adds are ruff clean. ruff reports six findings in tr_sig.py; the rule counts are identical on main (4 E501, 1 F841, 1 I001) and none of the six is on a line this changes.


Generated by Claude Code

@github-actions

github-actions Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Contributor Check: UNKNOWN

Check Result
Profile UNKNOWN
Credential LOW
Overall UNKNOWN

Automated check by AgenTrust Contributor Check.

@github-actions github-actions Bot added the needs-review:UNKNOWN Contributor check flagged UNKNOWN risk label Aug 21, 2026
@lywinged
lywinged force-pushed the fix/tr-sig-message-code-mismatch branch from cf8f5a7 to bf308e8 Compare August 23, 2026 07:54
@lywinged
lywinged marked this pull request as ready for review August 23, 2026 18:20
@lywinged
lywinged requested a review from a team as a code owner August 23, 2026 18:20
…t code

report.py publishes both halves: the JSON artifact carries `code` per finding
and the HTML table prints it beside the text. They disagreed. `_verify_ed25519`
returned messages prefixed with TR-SIG-001, TR-SIG-002 and TR-SIG-003, and two
callers attached those messages to findings of their own, `check` under
TR-SIG-005 and `check_cmcp_runtime` under TR-SIG-001. A code written in that
helper could only ever match one of the two, and matched neither.

Measured across every module over a mutation sweep, four distinct instances,
all of them in tr_sig:

    code=TR-SIG-005  message names TR-SIG-001: signature verification failed
    code=TR-SIG-005  message names TR-SIG-002: invalid public key in cnf.jwk.x
    code=TR-SIG-005  message names TR-SIG-003: invalid base64url signature
    code=TR-SIG-001  message names TR-SIG-002: invalid public key in cnf.jwk.x

So a malformed signature was forwarded as a TR-SIG-005 finding reading
"TR-SIG-003", naming a code the suite had not used and the docs described as
something else.

The helper's messages now name no code. Nothing else changes about them: the
reason for the failure is still there, and Finding.code is the only place a
code appears. Naming a module's own code in its message is the convention
everywhere else here and is left alone.

TR-SIG-003 had no other user. It was never a Finding.code anywhere; that
message prefix was the whole of its existence, so removing it leaves a code
documented in three pages and emitted by nothing. The rows are removed and the
condition they described, a signature that does not verify, is reported by
TR-SIG-005 for a plain record and TR-SIG-001 for a cmcp claim, both of which
are documented.

That also forced the two rows next to it to be read again. TR-SIG-001 was
documented as "Signature algorithm is not Ed25519", which nothing reports under
that code: in check_cmcp_runtime it means a missing signature or the
verification outcome itself. TR-SIG-002 was "cnf.jwk missing or malformed";
it means a key that is not OKP/Ed25519 or a missing x, and only in the cmcp
path. Both rows now say so, in docs/error-codes.md and docs/modules/tr-sig.md,
and the Level 0 failure list and the tutorial's reference table follow, since
both cited cmcp-only codes for plain records.

tests/test_findings_are_self_consistent.py asserts that no finding's message
names a code other than its own, over both entry points and a mutation set
aimed at the error branches rather than at breadth. Verified by restoring each
of the three prefixes on its own: each fails the guard, and each fails it in
both the plain and the cmcp case.

It counts the findings it looked at and fails on none. Its first version did
not: every record is skipped when a module raises, so a module that raised on
all of them reported a pass over nothing, measured by making check raise before
its first finding. That is the same degradation the sample guard on the
error-code branch was hardened against, reproduced one branch later.

The docs guard from the error-code branch needed strengthening to see any of
this. It matched codes by regex over module file text, so the docstring added
here explaining why TR-SIG-003 must not appear in a message was, on its own,
enough to keep the deleted code looking alive: with the row removed and no
emitter left, the guard still passed. It now reads string literals through
`ast`, skipping module, class and function docstrings, and comments are dropped
by the parse. Verified in both directions: re-adding the dead row fails, and
documenting a code no module emits fails; and on a probe carrying a code in a
module docstring, a function docstring, a comment and a real literal, only the
literal is seen.

Removing the prefix also settles that guard's own rationale, which said the
named set and the Finding.code set differ and gave TR-SIG-003 as the example.
Measured after this change they are identical, so the docstring now says that
and gives the reason the match stays on literals: it reads source text and
cannot tell which literal reaches a Finding.

Unrelated to the codes but found while checking where they appear:
docs/quickstart.md documented an output format the CLI does not produce. Its
sample put the error code in the first column, where the CLI prints the module,
and none of its three messages exists anywhere in the source. Replaced with
output from a real run, and the surrounding text now says where a code does and
does not appear.

This branch is stacked: the error-code documentation branch first, then the
raising-on-a-malformed-record branch, then this one. Each contains the commits
below it.

225 passed, 5 xpassed. The two test files this adds are ruff clean. ruff reports
six findings in tr_sig.py; the rule counts are identical on main, 4 E501, 1
F841 and 1 I001, and none of the six is on a line this changes.

Signed-off-by: LouieLuNZ <48041247+lywinged@users.noreply.github.com>
@lywinged
lywinged force-pushed the fix/tr-sig-message-code-mismatch branch from bf308e8 to 088b99b Compare August 23, 2026 18:21

@imran-siddique imran-siddique 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.

Reviewed after #74 and #75 re-scoped the stack. The helper now returns code-neutral reasons because its two callers deliberately assign different finding codes; the dead TR-SIG-003 documentation is removed; plain TRACE and cMCP-specific code mappings match their actual emitters; and the guards reject both foreign-code messages and zero-findings false positives. All Python and CodeQL checks are green.

@imran-siddique
imran-siddique merged commit d9c0da1 into agentrust-io:main Aug 23, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-review:UNKNOWN Contributor check flagged UNKNOWN risk

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants