Repository navigation
content_marking: enforce subject, eat_profile, anchor, and strict UTF-8 / RFC 8259 JSON boundaries (#555) - #565
Conversation
…SON boundaries Closes agentrust-io#555 Signed-off-by: Raghuvar Durga <177662415+drk-ram@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
rajnisht7
left a comment
There was a problem hiding this comment.
Overall the direction looks good, the assertion-side checks, error taxonomy (ContentMarkingError vs RecordMismatch), UTF-8/BOM/NaN/Infinity rejection, and nested surrogate handling all seems correct
a few things that needs to be looked upon:
- the str(exc).startswith(what) logic in _loads is brittle as it rewrites some ContentMarkingErrors (like duplicate members) but not others (like non-finite numbers) based purely on the message text. Just let ContentMarkingError propagate and only wrap the real ValueError/RecursionError cases. Also add a regression test that feeds duplicate-key JSON through the new _loads path.
- contract the inconsistency around bytes vs bytearray. The code accepts both, but the error message says “must be bytes” and the new test is named test_loads_rejects_non_bytes_input, either document + test bytes | bytearray properly, or tighten to pure bytes.
- _has_surrogate currently rejects any code unit in U+D800–U+DFFF which means a valid surrogate pair passed as a Python string (e.g. an emoji constructed that way) gets rejected on the direct anchor= path, even though the JSON-bytes path accepts it after json.loads. Worth an explicit decision + test.
- after a successful hash match, the fetched record only gets presence + equality checks on subject/eat_profile. No type or _SUBJECT_RE re-check. Not a bypass (equality fails against a non-string), but the error framing and symmetry would be cleaner if you validated the record side too.
- BOM rejection is a deliberate library choice (RFC 8259 only says generators must not emit it; parsers may ignore). The companion spec currently has no text about this either note it as a TRACE profile decision or add a short normative line
- test: a couple of tests mix two scenarios, theres no positive bytearray control through the new _loads, and the duplicate-key path isnt exercised by the new suite
- also the CHANGELOG entry is missing
dinakarjs
left a comment
There was a problem hiding this comment.
Independent boundary review of c495cb1. I reran the 385 content-marking/public-error/regex/untrusted-input tests and added 23 local controls, all passing; imports were explicitly pinned to this checkout. Current CI, Review bar, Conformance CI and CodeQL report success.
The additional controls exercised escaped duplicate names (including nested objects inside arrays) through both public APIs, bytes and bytearray with sha256/sha384, raw/escaped supplementary Unicode, an interior U+FEFF, exact-byte hashing across a trailing newline, and malformed payloads with matching versus stale hashes. Duplicate-member refusal remains intact on this head. Malformed bytes with a stale hash correctly raise RecordMismatch before parsing; after updating the hash to those same malformed bytes the parser raises ContentMarkingError rather than accusing the server of a changed record.
One clarification for the existing surrogate discussion: a Python scalar U+1F600 works through the direct anchor path and escaped JSON surrogate pairs decode to that scalar. A Python string containing two explicit surrogate code points is refused on both direct build and verify paths. Keeping that refusal is defensible because that string is not UTF-8 encodable under strict encoding; accepting the pair should be an explicit normalization decision, not an automatic requirement. Please retain separate positive tests for the actual scalar and JSON escape representation whichever contract is chosen.
I found no additional blocking defect in these controls. This is a bounded COMMENT review, not approval of unresolved author follow-ups: Rajnish's existing error-message/contract/profile-documentation points remain for the author and maintainers to resolve. The positive bytearray, nested duplicate and Unicode controls above would be useful committed regressions.
|
Tests-only companion for the author to adopt on exact head c495cb1: dinakarjs@4e65626 (DCO-signed, one new test file; original PR/implementation ownership retained). The 22 paired public-API controls commit the useful boundaries from the earlier review: positive bytearray build/verify and exact SHA256/SHA384 hashes; escaped duplicate member names at top level, nested objects and inside arrays through build and hash-matching verify; actual supplementary Unicode scalar versus JSON escape-pair spelling; interior U+FEFF versus a leading UTF-8 BOM; valid direct scalar anchor; trailing-newline exact-byte binding; stale-hash RecordMismatch versus hash-matching malformed-JSON ContentMarkingError. Validation: 445 related content-marking/public-error/regex/untrusted-input tests passed on Python 3.12.14, including these 22 controls. Import resolution uses this checkout. Ruff, strict mypy on the new file (MYPYPATH=src), repository dash and whitespace checks pass. The published tree matches the tested local tree. Isolated mutants are detected: replacing the duplicate-member hook with dict fails 6 controls; stripping trailing newlines before hashing fails 2; rejecting an interior U+FEFF as a leading BOM fails 2. Positive paired controls remain in each test. These tests do not prescribe current diagnostic-wrapper wording or normalize Python strings containing explicit surrogate code points. Rajnish's error-propagation, bytes/bytearray documentation, fetched-record validation, BOM profile rationale and changelog follow-ups remain for the author/maintainers. This is a companion handoff, not approval of those unresolved items or a claim that this commit has landed upstream. |
Add 22 public-API paired controls offered for PR agentrust-io#565. Retain exact-byte hash semantics and distinguish malformed fetched JSON from stale hashes. Signed-off-by: Srinivasa Consultants <78673153+dinakarjs@users.noreply.github.com>
… and spec Signed-off-by: Raghuvar Durga <177662415+drk-ram@users.noreply.github.com> Co-authored-by: Srinivasa Consultants <78673153+dinakarjs@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
dinakarjs
left a comment
There was a problem hiding this comment.
Re-reviewed exact head 06ddb48. The author follow-ups from the previous review are addressed: ContentMarkingError propagates by type rather than message prefix, bytes/bytearray are documented and tested consistently, valid UTF-8 scalars/JSON surrogate escapes are distinguished from explicit Python surrogate code points, resolved-record fields are validated before equality classification, and the companion specification/changelog state the strict JSON boundary.
The focused content-marking/public-error/regex/untrusted-input selection passes 417 tests with this head's hash-locked dependencies and checkout imports. It includes the supplementary controls I contributed for nested/escaped duplicate members, both digest algorithms, bytearray positives, scalar versus escaped-byte identity, interior versus leading BOM, trailing newlines, and stale-hash versus hash-matching malformed bytes. I found no remaining blocking defect in this bounded code review.
Ruff, strict mypy on the production module and both changed test modules, dash and whitespace checks pass. CI and Conformance CI succeed; ClusterFuzzLite was still running when checked. I contributed the supplementary tests; this technical approval does not replace another maintainer's assessment of that contribution, any specification-process decision, or full record/asset/hardware verification.
|
Thanks @rajnisht7 and @dinakarjs for the thorough review and companion test suite! All feedback items have been addressed in \4b4de13\ and \ |
There was a problem hiding this comment.
Reviewed the head 06ddb48, the changes looks good,there are two things worth fixing before merge:
- bytearray TOCTOU: The dual-read (hash vs parse on the same object) already existed on main. But the latest commits (06ddb48 especially) made bytes | bytearray the official public contract and added tests for it. So the risk became real and documented
- Missing fields -> RecordMismatch inconsistency: The old presence check (RecordMismatch) was already there and this PR adds the new type/format checks that raise ContentMarkingError which creates the asymmetry.
nit:
- kindly remove the issue and PR number from the docstring, top of the test files
|
Follow-up to the current-head mutable-input/error-classification review: a DCO-signed companion is ready for author adoption on exact head Both public entry points now freeze one immutable record-bytes snapshot before their first parse/hash read, so a caller mutation between those reads cannot bind one version and inspect another. Deterministic mutation-at-the-seam controls cover build and verify for SHA-256 and SHA-384, with valid twins and stale-hash refusals. The companion also changes hash-matching records missing subject/eat_profile to Validation on Linux/Python 3.12.14 with the current head's hash-pinned development dependencies: 4,271 root tests passed, 48 skipped; 429 focused tests passed; repository Ruff/mypy, targeted strict mypy, dash and whitespace checks passed. Removing only the build snapshot causes 2 assertion failures; removing only the verify snapshot causes 2; restoring the old missing-field exception causes 4 failures while the 4 null-field controls pass. The repository review-bar run against the parent reports 10 failures on behavior, 0 on missing names/files, and 120 passes/skips. Published tree This supersedes my earlier no-blocker conclusion specifically for these newly identified mutable-input/error boundaries. Author incorporation, current-head CI/re-review and maintainer disposition remain open; this companion does not change the upstream PR head or merge it. Implemented with OpenAI Codex assistance. |
Parse and hash one immutable bytes snapshot in build and verify. Classify hash-matching missing required fields as malformed, matching other invalid fields. Preserve RecordMismatch for hash or well-formed field disagreement. Add deterministic seam-mutation controls for both digest algorithms and paired malformed-record controls. Remove issue numbers from test docstrings. Signed-off-by: Srinivasa Consultants <78673153+dinakarjs@users.noreply.github.com> Signed-off-by: Raghuvar Durga <177662415+drk-ram@users.noreply.github.com> Co-authored-by: Srinivasa Consultants <78673153+dinakarjs@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Thanks @rajnisht7 and @dinakarjs! Adopted \7948259\ (cherry-picked from \5495993358d978eac55bfdf10455782612b99f92):
|
dinakarjs
left a comment
There was a problem hiding this comment.
Rechecked exact head 7948259 after adoption of companion 5495993. Both public entry points take one immutable record-bytes snapshot before their parse/hash reads. Hash-matching records missing required fields now raise ContentMarkingError, consistent with other malformed fields; hash disagreement and well-formed field disagreement retain RecordMismatch. The changelog and test docstrings also reflect the intended correction.
The 429 focused content-marking/public-error/regex/untrusted-input tests pass on Linux/Python 3.12.14 with this head's hash-locked development dependencies and imports pinned to this checkout, including the deterministic build/verify seam controls for SHA-256 and SHA-384 and missing/null-field controls. All returned current-head technical workflows succeed, including ClusterFuzzLite (Dependabot auto-merge is skipped).
This closes my companion-adoption/recheck action for these two boundaries. I authored the adopted correction and tests, so this is a COMMENT rather than approval of my own contribution. Rajnish and an independent maintainer should assess the exception-subclass compatibility choice and final head before merge. No merge, release, or broader record/asset/hardware acceptance is claimed.
imran-siddique
left a comment
There was a problem hiding this comment.
Thanks @drk-ram and @dinakarjs. Approving as sponsoring maintainer for the steps 3 and 5 wording in content-marking-v1. Both of @rajnisht7's points from 18 October are closed in 7948259: build and verify now read one immutable copy of the record bytes, and a hash-matching record missing a required field raises ContentMarkingError like any other malformed field. Rejecting unpaired surrogates here matches the anchor rule in #537.
Keeps both Unreleased entries in CHANGELOG.md (this change and #565). Signed-off-by: Imran Siddique <45405841+imran-siddique@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What this changes
Contributor: Raghuvar Durga (@drk-ram)
Closes #555
Enforces consumer field validation and strict UTF-8 / RFC 8259 JSON boundaries in
src/agentrust_trace/content_marking.pyandspec/content-marking-v1.md:_loads(data, what)strict UTF-8 and RFC 8259 JSON checks:bytesorbytearraywithContentMarkingError.datastrictly as UTF-8 (data.decode("utf-8")) and rejects a leadingU+FEFFUTF-8 byte-order mark (RFC 8259 section 8.1), refusing UTF-16, UTF-32, and UTF-8 BOM-prefixed payloads withContentMarkingError.NaN,Infinity,-Infinity) viaparse_constantwithContentMarkingError.ContentMarkingErrordirectly fromobject_pairs_hook(_no_duplicate_members) andparse_constant.U+D800toU+DFFF) in parsed strings and object keys withContentMarkingError, while preserving valid supplementary Unicode scalars (\U0001f600) and JSON surrogate-pair escapes (\ud83d\ude00).build_assertionandverify_assertionbytes | bytearraycontract and field validation:record_bytesasbytes | bytearrayacross_digest,build_assertion, andverify_assertion.build_assertion, checksisinstance(subject, str)before matching_SUBJECT_REand rejects UTF-16 surrogate code points inanchorwithContentMarkingError(tools/sweep_public_surface.pynow reports'\ud800'refused oncontent_marking.build_assertion.anchor).verify_assertion, validatessubject(SPIFFE or DID URI),eat_profile(non-emptystr), and optionalanchor(non-emptystrwithout UTF-16 surrogate code points) on bothassertion["data"]and the resolvedrecordwithContentMarkingErrorbefore comparing the two forRecordMismatch.spec/content-marking-v1.mdsection 4 (Verification) to state that record bytes are decoded as strict UTF-8 JSON without a leading byte-order mark, non-finite numeric constants, or unpaired UTF-16 surrogates, and that both the assertion and the resolved record carry a valid SPIFFE or DIDsubject, a non-empty stringeat_profile, and (when present) a non-empty stringanchor.CHANGELOG.mdentry under[Unreleased].Type of change
Spec section
spec/content-marking-v1.mdsections 2 and 4 (src/agentrust_trace/content_marking.py)Tests for a fix
Added
tests/test_content_marking_consumer_boundary.py(51 test cases) and cherry-picked companion regression controlstests/test_content_marking_review_controls.py(24 test cases, co-authored by @dinakarjs).Validation commands and results:
pytest tests/test_content_marking*.py tests/test_public_functions_raise_what_they_document.py tests/test_regex_surface_parity.py tests/test_untrusted_input_stays_on_the_documented_error.py: passed (417 passed)python tools/review_bar.py --base origin/main --head HEAD: passed (57 fail on behaviour, 0 fail on a missing name or file, 18 pass or skip)python tools/sweep_public_surface.py --strict: passed (89 parameters swept, 140 accepted junk values, 0 unfiled leak(s))python tools/check_dashes.py: passed (no dashes outside examples/, spec/trace-v0.1.md)ruff check src/agentrust_trace/content_marking.py tests/test_content_marking_consumer_boundary.py tests/test_content_marking_review_controls.py: passedmypy --strict src/agentrust_trace/content_marking.py tests/test_content_marking_consumer_boundary.py tests/test_content_marking_review_controls.py: passed (Success: no issues found in 3 source files)Checklist
git commit -s)CHANGELOG.mdupdated (for any normative change)<!-- CHANGED: #NNN: description -->in spec text