Skip to content

fix(content-marking): establish presence of the required binding fields - #335

Merged
imran-siddique merged 1 commit into
mainfrom
fix/content-marking-required-binding-presence
Sep 12, 2026
Merged

fix(content-marking): establish presence of the required binding fields#335
imran-siddique merged 1 commit into
mainfrom
fix/content-marking-required-binding-presence

Conversation

@imran-siddique

Copy link
Copy Markdown
Member

Closes #326.

verify_assertion() compared the duplicated binding fields with .get() equality. A comparison establishes that two reads agree, not that either exists, and two absences compare equal.

Reproduced at 0aefa1f, running @lywinged's six cases with record.hash recomputed over the modified bytes each time so every pair stays self-consistent:

before the fix                          after the fix
ACCEPTED                 subject absent on both sides        refused(ContentMarkingError)
ACCEPTED                 eat_profile absent on both sides    refused(ContentMarkingError)
ACCEPTED                 both fields absent on both sides    refused(ContentMarkingError)
refused(RecordMismatch)  assertion-only omission             refused(ContentMarkingError)
refused(RecordMismatch)  record-only omission                refused(RecordMismatch)
ACCEPTED                 valid pair                          ACCEPTED

The two single-side rows are what make the hole precisely mutual absence rather than something wider.

Why this layer has to establish its own shape

spec/content-marking-v1.md section 2 marks both fields required and section 6 says a conforming consumer checks both against the fetched record. verify_assertion() performs only the binding check and returns the parsed record before any Trust Record signature or schema verification, and a caller is allowed to run it on its own. Relying on a later verifier to reject the record is not available to it.

Which exception, and why it differs by side

  • Assertion missing a required field: ContentMarkingError. A malformed assertion is the caller's own input. RecordMismatch means "the URL is serving something else" and would point the reader at whoever operates that server, which is the reasoning test_an_int_no_longer_reports_a_record_mismatch already pins in this file.
  • Record missing a required field: RecordMismatch. It matched the declared hash, so that URL really is serving something that is not a conformant record.
  • Two present values that disagree: unchanged.

The only behaviour change for input that was already refused is the class on an assertion-side omission, from RecordMismatch to its ContentMarkingError parent. No caller catching the documented contract loses anything, and one catching RecordMismatch specifically was being told the wrong party was at fault.

Verified in both directions

  • With the fix: tests/test_content_marking.py 43 passed, repository 1437 passed.
  • Against origin/main's content_marking.py: 7 of the new cases fail.
  • The pair helper recomputes the hash. Without that every case would fail at the digest check and pass for the wrong reason, so the helper's docstring says so.
  • The four repository-wide failures in test_generators_reproduce_fixtures.py and test_safe_integer_range.py are pre-existing Windows path-separator artifacts.

No overlap with #325, which touches build_assertion only.

Reported by @altrudev. Reproduced independently by @lywinged, whose six-case matrix and check against #325's head is what this PR's coverage is built from.

🤖 Generated with Claude Code

https://claude.ai/code/session_01X27MBo6tGVw1UmmchuGFnW

verify_assertion() compared the duplicated binding fields with
`record.get("subject") != data.get("subject")` and the same line for
eat_profile. A comparison establishes that two reads agree, not that either
exists, and two absences compare equal. A peer-produced assertion omitting
data.subject, paired with a hash-matching record that also omitted subject,
agreed by mutual absence and the function returned the parsed record as a
successful binding.

spec/content-marking-v1.md section 2 marks both fields required and section 6
says a conforming consumer checks both against the fetched record. This layer
performs only the binding check and returns before any Trust Record signature
or schema verification, and a caller is allowed to run it on its own, so it has
to establish its own required shape rather than relying on a later verifier.

Presence is now checked on both sides. An assertion missing a required field is
ContentMarkingError, because a malformed assertion is the caller's own input and
RecordMismatch would point the reader at whoever serves the URL, which is the
reasoning test_an_int_no_longer_reports_a_record_mismatch already pins. A record
missing one is RecordMismatch, because it matched the declared hash and that URL
really is serving something that is not a conformant record. Two present values
that disagree are unchanged.

Regression coverage carries all six cases from the reproduction, including the
two single-side controls that make the hole precisely mutual absence, plus a
complete-pair control. The pair helper recomputes the hash, without which every
case would fail at the digest check and pass for the wrong reason.

Reported by @altrudev in #326, reproduced independently by @lywinged with the
six-case matrix and the check against #325's head.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X27MBo6tGVw1UmmchuGFnW
@imran-siddique
imran-siddique requested review from a team and lywinged as code owners September 12, 2026 04:35
@imran-siddique
imran-siddique merged commit 2a82bef into main Sep 12, 2026
9 checks passed
@imran-siddique
imran-siddique deleted the fix/content-marking-required-binding-presence branch September 12, 2026 04:41
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.

content_marking.verify_assertion accepts missing required binding fields when both sides omit them

1 participant