Skip to content

fix(provenance): establish cnf object boundary - #252

Open
altrudev wants to merge 2 commits into
agentrust-io:mainfrom
altrudev:fix/provenance-cnf-object-boundary-251
Open

fix(provenance): establish cnf object boundary#252
altrudev wants to merge 2 commits into
agentrust-io:mainfrom
altrudev:fix/provenance-cnf-object-boundary-251

Conversation

@altrudev

Copy link
Copy Markdown

What

Closes #251.

provenance.verify_record() now establishes cnf through the existing _as_object() boundary before reading jwk.

This keeps malformed externally supplied cnf values inside the verifier's documented ProvenanceError rejection boundary instead of:

  • leaking AttributeError for truthy non-object values; or
  • laundering falsey non-object values through (... or {}) as though cnf were absent.

Missing cnf, explicit null, an empty object, and a valid embedded JWK keep their existing behavior.

Scope

No schema or normative specification change. No new validator is introduced; this reuses the object-boundary helper already used for identity and tool_catalog in the same function.

Regression evidence

The added matrix signs the actual malformed/edge records with the trusted key before verification, so a rejection cannot be attributed to an unrelated invalid signature.

Covered controls:

  • truthy non-objects: string, list, integer, boolean;
  • falsey non-objects: empty string, empty list, zero, false;
  • missing / null / {} controls;
  • valid embedded cnf.jwk control.

Mutation property: restoring the old (record.get("cnf") or {}).get("jwk") path makes the truthy vectors escape through host-language exceptions and the falsey vectors cease to be rejected.

Non-claim

This is exception-safety and API-contract consistency in the MCP provenance verifier. It does not change provenance trust semantics or claim a cryptographic vulnerability.

AI-assistance disclosure: ChatGPT assisted with source triage, adversarial matrix design, and drafting. altrudev reviewed the bounded claim and remains responsible for the contribution.

Signed-off-by: altrudev <266135212+altrudev@users.noreply.github.com>
Signed-off-by: altrudev <266135212+altrudev@users.noreply.github.com>
@github-actions

github-actions Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

🟡 Contributor Check: MEDIUM

Check Result
Profile MEDIUM
Credential LOW
Overall MEDIUM

Automated check by AgenTrust Contributor Check.

@github-actions github-actions Bot added the needs-review:MEDIUM Contributor check flagged MEDIUM risk label Aug 30, 2026
@altrudev
altrudev marked this pull request as ready for review August 30, 2026 18:02
@altrudev
altrudev requested a review from a team as a code owner August 30, 2026 18:02

@lywinged lywinged left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reproduced on a clean checkout at b37519e. Four gates green locally: ruff check src tests scripts, tools/check_dashes.py, mypy src/agentrust_trace (Success: no issues found in 10 source files), pytest at 1027 passed, 1 skipped.

The mutation property holds as you state it. Restoring (record.get("cnf") or {}).get("jwk") and
changing nothing else: 8 failed, 1019 passed, 1 skipped. Running the eight parameters against
main gives the split your body describes:

'not-an-object'  AttributeError: 'str' object has no attribute 'get'
['unexpected']   AttributeError: 'list' object has no attribute 'get'
1                AttributeError: 'int' object has no attribute 'get'
True             AttributeError: 'bool' object has no attribute 'get'
''               accepted, verify_record returns
[]               accepted, verify_record returns
0                accepted, verify_record returns
False            accepted, verify_record returns

On the falsey half verify_record returns normally, so those records verified rather than merely
skipping a check.

One addition to the evidence rather than a correction to it. The mutation you state is the old
path. There is a second mutant these tests kill, and it is the more plausible wrong fix:

_c = record.get("cnf")
cnf = _as_object(_c, "cnf") if _c else {}

That reads as faithful, and it closes the AttributeError in the issue title. It passes the four
truthy parameters and fails exactly the four falsey ones, 4 failed, 8 passed in the new file. So
the falsey half of the matrix is not only covering the second symptom, it is what stops a wrong
fix from looking right. That is the part of this PR I would least want trimmed in review.

Two checks for whoever else reads this.

sign.verify_record already refuses all eight, as ValueError, at the validate_json
schema step (sign.py:518):
record does not conform to the TRACE v0.2 schema at cnf: '' is not of type 'object'.
provenance.verify_record has no schema step, so an explicit guard is the only means it has. The
two verifiers now agree on this field, each by the mechanism available to it, and there is nothing
for this PR to do in sign.py.

spec/server-provenance-v1.md line 106 carries cnf.jwk as required: yes, so no record the
spec calls valid is newly refused. Sweeping what verify_record reads off the record once this
lands, signature is the only one still used as a type nothing has established. Separate from
this PR and not a reason to hold it.

Approving.

Tool-assisted: the matrix, the mutants and this write-up.

@imran-siddique

imran-siddique commented Aug 31, 2026

Copy link
Copy Markdown
Member

Batch response for this cluster is here: agentrust-io/agent-manifest#357 (comment)

Short version: the finding class is real and welcome. Your CI had never run, held under first-time-contributor gating, until I released 36 runs across your PRs an hour ago, and five of your eight are now red. Please fix those, sequence trace-spec#258 against #252 which touch the same two files, and tell me the order you want them reviewed in.

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

Labels

needs-review:MEDIUM Contributor check flagged MEDIUM risk

Projects

None yet

Development

Successfully merging this pull request may close these issues.

provenance.verify_record still leaks AttributeError on malformed cnf

3 participants