Fix cross-runtime fingerprint canonicalization#135
Merged
Conversation
| return "99" | ||
|
|
||
|
|
||
| class _WeirdFloat(float): |
| try: | ||
| m._canonical_fingerprint_json({"x": bad}) | ||
| nonfinite_rejected = False | ||
| except ValueError: |
| try: | ||
| m._canonical_fingerprint_json(bad) | ||
| unsafe_int_rejected = False | ||
| except ValueError: |
| try: | ||
| m._canonical_fingerprint_json({"n": bad}) | ||
| unsafe_int_rejected = False | ||
| except ValueError: |
| try: | ||
| m._canonical_fingerprint_json(_WeirdFloat(bad)) | ||
| weird_float_nonfinite_rejected = False | ||
| except ValueError: |
| try: | ||
| m._canonical_fingerprint_json({_WeirdStr("\ud800"): "x"}) | ||
| weird_str_surrogate_rejected = False | ||
| except ValueError: |
| try: | ||
| m._canonical_fingerprint_json(bad) | ||
| surrogate_rejected = False | ||
| except ValueError: |
| try: | ||
| m._canonical_fingerprint_json({bad: "x"}) | ||
| surrogate_rejected = False | ||
| except ValueError: |
| try: | ||
| m._canonical_fingerprint_json({"k": bad}) | ||
| surrogate_rejected = False | ||
| except ValueError: |
There was a problem hiding this comment.
Pull request overview
This PR fixes cross-runtime fingerprint verification drift by switching the Python harnesses from json.dumps(sort_keys=True) to a direct RFC 8785 / JCS-style canonical JSON writer, and by defining the decoded canonical token bytes as the authoritative digest input for verifiers.
Changes:
- Implement a JCS-style canonical JSON encoder for fingerprint token/digest generation (including ES NumberToString numeric form and UTF-16 key ordering).
- Update fingerprint self-tests with cross-runtime adversarial vectors (numeric edge cases, Unicode, duplicate-key normalization collisions, and optional Node bridge verification).
- Expand Docker documentation to clarify the verifier contract (hash decoded token bytes; re-canonicalization requires an RFC 8785/JCS direct writer + IEEE-754 model).
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| scripts/run_strategy.py | Replaces legacy json.dumps(sort_keys=True) fingerprinting with a JCS-style canonical writer and hashes the resulting UTF-8 token bytes. |
| docker/run_json.py | Mirrors the same canonical fingerprint implementation and updates the fingerprint contract comments. |
| scripts/fingerprint_self_test.py | Adds extensive deterministic and adversarial regression coverage, plus an optional Node bridge check when node is available. |
| docker/README.md | Documents the canonical fingerprint JSON rules and the “hash decoded token bytes” verifier contract. |
Comments suppressed due to low confidence (2)
scripts/run_strategy.py:497
- _canonical_fingerprint_json uses
value.keys()andvalue[orig]for dict handling, which can invoke overriddenkeys()/__getitem__ondictsubclasses and change canonicalization output. Since the helper explicitly bypasses hostile subclass hooks forint/float/str, consider similarly forcing the basedictslots for key iteration and value lookup to keep canonical token bytes/digest stable under adversarial dict subclasses.
for k in value.keys():
if not isinstance(k, str):
raise TypeError(
"fingerprint JSON object keys must be str, "
f"got {type(k).__name__}")
docker/run_json.py:509
- _canonical_fingerprint_json uses
value.keys()andvalue[orig]for dict handling, which can invoke overriddenkeys()/__getitem__ondictsubclasses and change canonicalization output. Since the helper explicitly bypasses hostile subclass hooks forint/float/str, consider similarly forcing the basedictslots for key iteration and value lookup to keep canonical token bytes/digest stable under adversarial dict subclasses.
for k in value.keys():
if not isinstance(k, str):
raise TypeError(
"fingerprint JSON object keys must be str, "
f"got {type(k).__name__}")
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+482
to
+484
| if isinstance(value, list): | ||
| return "[" + ",".join( | ||
| _canonical_fingerprint_json(v) for v in value) + "]" |
Comment on lines
+494
to
+496
| if isinstance(value, list): | ||
| return "[" + ",".join( | ||
| _canonical_fingerprint_json(v) for v in value) + "]" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a cross-runtime fingerprint integrity mismatch where Python canonical JSON
encoded integral floats such as
12345.0, while JavaScript parsed and re-emittedthem as
12345. The provenance was semantically identical, but recomputing thedigest from the public MCP response could disagree with the engine-produced
digest.
json.dumps(sort_keys=True)fingerprint bytes with a directRFC 8785/JCS-style canonical writer
Unicode, and fail-closed invalid inputs
Node bridge regressions
This is generic fingerprint infrastructure only; it adds no strategy-specific
behavior.
Compatibility
The
{token,digest,provenance}shape and semantic provenance values areunchanged. Canonical token bytes and therefore digests rotate under the
corrected contract. Exact Python integers outside
[-(2^53-1), 2^53-1],non-finite numbers, unpaired surrogates, and normalized duplicate object keys
fail closed; existing callers return no fingerprint for such provenance.
Verification
scripts/fingerprint_self_test.py: 273/273 passedfinite values)
ctest -j 48: 120/120 passedgit diff --checkandpy_compilepassed