fix(content-marking): validate record.url external references - #284
fix(content-marking): validate record.url external references#284altrudev wants to merge 7 commits into
Conversation
|
🔴 Contributor Check: HIGH
Automated check by AgenTrust Contributor Check. |
lywinged
left a comment
There was a problem hiding this comment.
Approved on e15651d over fc38496. Reproduced on main: build_assertion(url=True), and verify_assertion() with record.url set to true or to an ftp:// value, all pass. On this head all three are refused and the two positive controls pass; the PR's own test file has 20 of 23 cases failing against main and none here. Beyond the PR's matrix I put 24 more inputs through _record_url(): it accepts the RFC 3986 forms, upper-case scheme, userinfo, IPv6 literals, ports 0 to 65535, a trailing-dot host, and refuses port 65536, an empty port, whitespace and %zz. One behaviour the body does not name: a non-ASCII host such as https://例子.测试/r is refused while its punycode form is accepted. That is the right call for a C2PA URI, and it means a producer with an IDN host now has to encode it, so it belongs in the body. rfc3986_validator is already a declared dependency. Merges cleanly with #282; the combined tree passes 1,197. Suite 1,190 passed, 1 skipped; ruff, mypy and the dash check clean.
Tool-assisted: the runs and this write-up.
Closes #283.
What
spec/content-marking-v1.mdsays the assertion'srecordobject follows C2PA's external-reference shape, but the reference implementation previously treatedrecord.urlas truthiness only in both producer and consumer paths.That allowed truthy non-string values and malformed/non-HTTP references to be emitted or accepted.
This change adds one shared
_record_url()boundary and uses it from bothbuild_assertion()andverify_assertion().The boundary requires:
httporhttpsscheme;It deliberately does not add broader URL policy beyond the external-reference boundary.
Regression coverage
Positive controls:
Refusals on both producer and verifier paths:
A separate mutation-oriented test records the original failure class: several malformed values are truthy, so restoring the old
if not url/if not ref.get("url")checks would reopen the boundary.DDC Radial review
The first candidate used
urllib.parse.urlsplit(). The repository's offline invariant deliberately forbids importing network-adjacent standard-library modules, so CI correctly rejected that approach even though the focused URL tests passed. The implementation was replaced with the repository's already-installedrfc3986-validatorplus a narrow authority/port boundary.A second CI pass then caught that
rfc3986-validatordoes not ship typing metadata. The final import carries a narrowly scoped# type: ignore[import-untyped]; mypy is not weakened globally.The final exact head
e15651d9f6a23c04f2d07e111563b9e76e36bd6epassed both repository CI and CodeQL (CI run 607, CodeQL run 407).Scope
Content-marking producer/consumer validation only. No change to hashing, Trust Record verification, C2PA signature handling, or TRACE wire format.
AI-assistance disclosure: ChatGPT assisted with source triage, adversarial-case design, implementation drafting, CI failure analysis, and diff review.
altrudevreviewed the bounded claim and remains responsible for the contribution.