Repository navigation
Closed
Description
What is wrong
Contributor: Raghuvar Durga (@drk-ram)
On main (63e38c6), src/agentrust_trace/content_marking.py has two verification boundary gaps across build_assertion, verify_assertion, and _loads:
- Producer-consumer asymmetry in
verify_assertionforsubject,eat_profile, andanchor:build_assertion(lines 173-204) enforces thatanchor(when present) is a non-emptystr,eat_profileis a non-emptystr(content_marking.build_assertion accepts truthy non-string eat_profile values #399), andsubjectmatches_SUBJECT_RE(spiffe://...ordid:...). (Note also thatbuild_assertionline 193 calls_SUBJECT_RE.match(str(subject or ""))without checkingisinstance(subject, str)first.)verify_assertion(lines 274-307), however, checks onlyfield not in dataandrecord.get(field) != data.get(field). It never validates thatsubjectis a string matching_SUBJECT_RE, thateat_profileis a non-emptystr, or thatanchor(when present indata) is a non-emptystr.- As a result,
verify_assertionaccepts malformed assertions whenever the assertion and record agree on the invalid value:
import hashlib, json
from agentrust_trace.content_marking import verify_assertion
raw_bad = json.dumps({"subject": 123, "eat_profile": "", "anchor": ""}).encode("utf-8")
bad_assertion = {
"label": "com.agentrust-io.trace",
"data": {
"version": 1,
"record": {
"url": "https://example.com/r.json",
"alg": "sha256",
"hash": "sha256:" + hashlib.sha256(raw_bad).hexdigest(),
},
"subject": 123,
"eat_profile": "",
"anchor": "",
},
}
verify_assertion(bad_assertion, raw_bad)
# Returns: {'subject': 123, 'eat_profile': '', 'anchor': ''}- Non-UTF-8 BOM, non-RFC-8259
NaN/Infinity, and unpaired UTF-16 surrogates in_loadsand string fields:_loads(data, what)(lines 77-86) passes rawbytesdirectly tojson.loads(data, object_pairs_hook=_no_duplicate_members)without decoding as strict UTF-8 (RFC 8259 §8.1, including rejecting a leadingU+FEFFBOM) or passingparse_constantto rejectNaN,Infinity, and-Infinity.- Furthermore, unpaired UTF-16 surrogates (
U+D800toU+DFFF) inrecord_bytesorassertion(such asanchor="\ud800", visible intools/sweep_public_surface.py'sACCEPTEDcolumn forcontent_marking.build_assertion.anchor) are accepted by bothbuild_assertionandverify_assertion:
import json
from agentrust_trace.content_marking import build_assertion, verify_assertion
utf16_bytes = json.dumps({
"subject": "did:web:example.com",
"eat_profile": "tag:agentrust-io.com,2026:trace-v0.2",
}).encode("utf-16")
a_utf16 = build_assertion(utf16_bytes, url="https://example.com/r.json")
verify_assertion(a_utf16, utf16_bytes) # Succeeds on UTF-16 BOM bytes!
nan_bytes = b'{"subject":"did:web:example.com","eat_profile":"tag:agentrust-io.com,2026:trace-v0.2","iat":NaN}'
a_nan = build_assertion(nan_bytes, url="https://example.com/r.json")
verify_assertion(a_nan, nan_bytes) # Succeeds on NaN!Spec section or file
spec/content-marking-v1.md§2 and §4src/agentrust_trace/content_marking.py(_loads,build_assertion,verify_assertion)
Expected behavior
verify_assertionshould validatesubject(isinstance(subject, str)and_SUBJECT_RE.match(subject)),eat_profile(isinstance(profile, str) and profile), andanchor(when"anchor" in data,isinstance(anchor, str) and anchorwith no unpaired UTF-16 surrogates) withContentMarkingErrorbefore comparing against the record's fields, matchingbuild_assertion.build_assertionshould requireisinstance(subject, str)before matching_SUBJECT_RE, and reject unpaired UTF-16 surrogates (U+D800toU+DFFF) in string arguments and parsed structures._loadsshould decodebytesstrictly as UTF-8 (rejecting non-UTF-8 encodings and a leadingU+FEFFbyte-order mark), rejectNaN/Infinity/-Infinityviaparse_constant, and reject unpaired UTF-16 surrogates withContentMarkingError.
Impact
Causes verify_assertion to accept C2PA assertions with non-URI/non-string subject, empty or non-string eat_profile, and empty or non-string anchor, and causes both build_assertion and verify_assertion to accept UTF-16/UTF-32 BOM-encoded payloads, NaN/Infinity constants, and unpaired UTF-16 surrogates.
Metadata
Metadata
Assignees
Labels
No labels
Activity
content_marking: enforce subject, eat_profile, anchor, and strict UTF…