You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
verify: verify_record leaves as TypeError when runtime.nonce is non-ASCII and expected_nonce is set #381
verify_record documents its contract as InvalidSignature for a bad signature and ValueError for every other rejection. With expected_nonce supplied and a runtime.nonce outside ASCII, it leaves as a TypeError instead:
TypeError: comparing strings with non-ASCII characters is not supported
sign.py:712 compares the two nonces with hmac.compare_digest(actual_nonce, expected_nonce), which refuses str operands containing non-ASCII characters. The schema types runtime.nonce as a string with a description saying base64url, and no pattern, so the record is schema-valid.
Reproducer, on the base record from #379 (same BASE, KEY, NOW):
body=json.loads(json.dumps(BASE)); body["runtime"]["nonce"] ="nønce"verify_record(sign_record(body, KEY), key_to_jwk(KEY), now=NOW, expected_nonce="a-nonce-value")
# TypeError: comparing strings with non-ASCII characters is not supported
The rule it tests: spec §3.2.2 "When a challenge nonce was issued, a record that omits or mismatches it MUST be rejected", and verify_record's own exception contract.
Spec section or file
src/agentrust_trace/sign.py:710-713
spec/trace-v0.2.md §3.2.2 (freshness, challenge-nonce binding) and §3.3 step 2
Expected behavior
ValueError("record runtime.nonce does not match expected_nonce"), reached by comparing the UTF-8 encodings (compare_digest(actual.encode(), expected.encode())) so the comparison stays constant-time and accepts any string. Alternatively the schema constrains nonce with a base64url pattern and the record is rejected earlier as schema-invalid; either way the outcome is one of the documented ones.
Impact
A caller that catches the documented exceptions and treats anything else as a verifier fault gets a fault from attacker-influenced input: the nonce is copied from the record. The record is rejected either way, so this is a contract escape rather than a wrong verdict.
Found by the differential run in #376 (mutation m41).
The line numbers in the body are this pull request's base (sign.py:710-713, 712). In the published 0.10.0 wheel the compare_digest call is sign.py:683; on main at ccbd5b9 it is 799. The quoted block is identical in all three, and the reproducer runs unchanged on the wheel. Noted by @lywinged on #376.
Activity
chernistry commentedon Sep 21, 2026
The line numbers in the body are this pull request's base (
sign.py:710-713,712). In the published 0.10.0 wheel thecompare_digestcall issign.py:683; onmainatccbd5b9it is799. The quoted block is identical in all three, and the reproducer runs unchanged on the wheel. Noted by @lywinged on #376.test(differential): retire the five divergences the reference no long…
feat(trust-record-verify): a TypeScript verifier for TRACE v0.2, scor…