Repository navigation
fix(trust-record-verify): reject non-object inputs in parentRecordHash, verifyDelegationLink, and bundleDigest (#616) - #622
Conversation
|
❔ Contributor Check: UNKNOWN
Automated check by AgenTrust Contributor Check. |
dinakarjs
left a comment
There was a problem hiding this comment.
Reviewed exact head 3098d7a. Checked the plain-object guards at parent-record, delegation-link and bundle digest boundaries and their return/throw contracts. Local npm test (including generated validators and TypeScript build): 161 passed. Python wrapper suite tests/test_trust_record_verify_citations_and_platform.py: 34 passed. Relevant hosted checks are successful. No blocking finding.
3098d7a to
3a50396
Compare
lywinged
left a comment
There was a problem hiding this comment.
Request changes. Reviewed at 3a50396 on c5d49a5.
What I checked rather than read:
- The reproduction in #616, on
mainatdcca4e0:parentRecordHash(123)andbundleDigest(123)resolve to the SHA-256 of the text123, andverifyDelegationLinkreturns a link for parent123. On this head all three reject:parentRecordHashandverifyDelegationLinkwithrecord_not_object,bundleDigestwithinvalid_argument. - The six inputs the description lists, through all three functions: all 18 calls reject, with the codes and messages the description gives.
npm ci && npm testintrust-record-verify: 162 of 162 tests pass on Node 20.20.0 and 22.22.0, and the test workflows, Node 24 included, are green at this head.- The differential job on Node 22: 1646 cases, 0 unexpected. Its TypeScript verdicts are byte-identical to
main's, 134 chain digests included. - CI's commands, run on 3.11 and 3.12: ruff,
check_dashes.pyand mypy are clean, andpytestgives 4787 passed, 48 skipped. TheLC_ALL=C,-OOand replay steps exit 0. - Removing the
parentRecordHashguard, or thebundleDigestguard, fails one Node test and one Python test.
The one thing.
checkRevocationBundle catches only canonicalization_failed from bundleDigest, so the new invalid_argument escapes it. A class instance or an Object.create({}) object carrying a valid bundle's members now throws instead of being reported. With the bundle and context of examples/revocation-bundle/01-fresh-well-inside-both-bounds.json, checkRevocationBundle(Object.assign(new (class Bundle {})(), bundle), options) returns unverified_for_revocation with cause bundle_malformed on main. This head throws invalid_argument. Passed to verifyRecord as revocationBundle, the same bundle now makes it throw where main resolves.
123 as the bundle still gets bundle_malformed: what throws now is an object that is not a plain one. The doc comment of checkRevocationBundle says "Every other defect in the bundle is an outcome, not an exception", #616 expects a bundle that is not a JSON object to be reported as bundle_malformed at step 1, and by this pull request's own bundleDigest guard these are not JSON objects, and Python's verify_record reports a non-dict mapping as bundle_malformed at /. This, right after the shape step, does all three:
if (!isPlainObject(bundle)) {
return unverified("bundle_malformed", { path: "/", error: "the bundle is not a plain JSON object" });
}With it, and a test that passes class-instance and Object.create({}) copies of the bundles of vectors 01 and 20, npm test passes 163 of 163, and the test fails on this head. Catching invalid_argument around bundleDigest instead gives back exactly what main does, which reports the copy of vector 20 at statements/0/log_id, after step 2; the check above also meets #616's step 1 and agrees with Python.
Things I would not hold the merge for:
- No test needs the
|| !isPlainObject(parent)clause. Without it, all 162 Node tests and the whole Python suite still pass, because a non-object parent reaches the guard inparentRecordHashanyway. Its only effect is order:verifyDelegationLink({}, 123)givesrecord_not_objecthere anddelegation_absentwithout it, and refusing the parent first is what #588'sverify_delegation_linkdoes. Addingawait rejects(verifyDelegationLink({}, bad), "record_not_object")to the new loop pins it. - The description and commit message say the delegation guards match Python
agentrust_trace.delegation, which is #588's module and not onmainyet. Against #588's head they part in one place:parent_record_hash(123, "md5")there refuses the algorithm first, whereparentRecordHash(123, "md5")here refuses the record. ThebundleDigesthalf holds:bundle_digestraisesValueErrorforNone,123,True,False,"record",[]and[1, 2], and gives the same digest for the vector 01 bundle.
Generated by Claude Code
|
Thank you @lywinged! Addressed all points in
|
3a50396 to
c94f4f9
Compare
lywinged
left a comment
There was a problem hiding this comment.
Request changes. Reviewed at c94f4f9 on e9ab302.
The three changes the first round asked for are in: the plain-object guard at the top of checkRevocationBundle, ahead of its schema check; the verifyDelegationLink({}, bad) case; and parentRecordHash checking the algorithm before the record. The new test for the first is the problem. npm test here, on Node 22, passes 164 of 165: checkRevocationBundle and verifyRecord report non-plain bundle objects as bundle_malformed reads 20-statement-for-another-log-rejected.json, and the vector in examples/revocation-bundle is 20-malformed-statement-on-another-log.json. With the name corrected it still fails, at verifyRecord(vec.record, ...): the vectors carry records, an array, so vec.record is undefined and verifyRecord throws record_not_object. With vec.records[0] as well, all 165 pass, and the test then pins the guard: without it, or with a typeof bundle === "object" guard in its place that the class instance and the Object.create({}) copy get past, the test fails where bundleDigest throws invalid_argument through checkRevocationBundle, which is the first round's symptom. The checklist line ticked when the pull request opened, that npm --prefix trust-record-verify test passes, does not hold for this head, and the trust-record-verify workflow runs that command on Node 20, 22 and 24.
What I checked rather than read:
- CI's commands, run here on 3.11 and 3.12: ruff, the dash check and mypy clean; the suite at 5221 passed and 98 skipped on both; the replay experiment, the two
LC_ALL=C PYTHONUTF8=0runs, thePYTHONOPTIMIZE=2command and the two--helpcalls pass. tests/test_trust_record_verify_citations_and_platform.py, 35 tests, fails one when theparentRecordHashguard or thebundleDigestguard is removed, and passes with the new shape guard removed, the parent clause removed or the algorithm order reversed, so the Python side pins the first round's two guards and none of this round's three changes.- Removing
|| !isPlainObject(parent)fromverifyDelegationLinkfails theverifyDelegationLink({}, bad)case withdelegation_absent, and moving the record check inparentRecordHashback ahead of the algorithm check fails theparentRecordHash(123, "md5")case. Onmain, with #588 merged, Python'sparent_record_hash(123, "md5")refuses the algorithm first, asparentRecordHashdoes here. tools/review_bar.pyreports the one changed Python test failing on behaviour at the merge base.
Generated by Claude Code
…h, verifyDelegationLink, and bundleDigest (agentrust-io#616) Require isPlainObject(record) in parentRecordHash (after algorithm validation, matching Python agentrust_trace.delegation.parent_record_hash) and isPlainObject(parent) in verifyDelegationLink, failing with record_not_object; require isPlainObject(bundle) in bundleDigest (failing with invalid_argument) and in checkRevocationBundle (reporting bundle_malformed at path "/" for non-plain objects such as class instances or Object.create({})); and add Node and Python-to-TypeScript tests. Closes agentrust-io#616 Signed-off-by: Raghuvar Karthik Durga <177662415+drk-ram@users.noreply.github.com>
c94f4f9 to
916cec5
Compare
|
@lywinged Rebased onto |
|
@drk-ram The two-line fixture correction is available for adoption on top of current c94f4f9: dinakarjs#7 (commit 97d3ebd). It uses the committed vector filename and vec.records[0]. On Node 24.22.1, npm test changes from 164 passed/1 failed to 165 passed/0 skipped; removing only the checkRevocationBundle plain-object guard makes the repaired test fail, and restoring it returns all 165 to passing. Runtime source is unchanged. The Node 20/22 and hosted matrices remain for normal review. Prepared with OpenAI Codex; DCO signed, original history preserved. |
Summary
isPlainObject(record)at entry toparentRecordHash(record, algorithm)andisPlainObject(parent)inverifyDelegationLink(child, parent, options)intrust-record-verify/src/delegation.ts, rejecting non-object inputs (null,123,"str",true,[1, 2],new Date(0)) withTraceVerificationError("record_not_object", "a Trust Record is a JSON object")to match Pythonagentrust_trace.delegation.parent_record_hashandverify_delegation_link.isPlainObject(bundle)at entry tobundleDigest(bundle)intrust-record-verify/src/revocation.ts, rejecting non-object inputs withTraceVerificationError("invalid_argument", "bundle must be a JSON object")to match Pythonagentrust_trace.revocation.bundle_digest.trust-record-verify/tests/units.test.mjsand Python-to-TypeScript conformance checks intests/test_trust_record_verify_citations_and_platform.py.Closes #616
Changelog entry
trust-record-verify: reject non-object inputs inparentRecordHash,verifyDelegationLink, andbundleDigest(trust-record-verify: reject non-object inputs in parentRecordHash, verifyDelegationLink, and bundleDigest #616).parentRecordHash(record)andverifyDelegationLink(child, parent)in@agentrust-io/trust-record-verifynow requireisPlainObject(record)andisPlainObject(parent)(rejecting withrecord_not_object), andbundleDigest(bundle)requiresisPlainObject(bundle)(rejecting withinvalid_argument), matchingagentrust_trace.delegationandagentrust_trace.revocation.bundle_digest(by @drk-ram).Checklist
pytestpasses (pytest -q)python tools/validate_examples.pypassesruff check .andmypy --strict srcpassnpm --prefix trust-record-verify testpassesgit commit -s, DCO)