reject duplicate cbor map keys on meta item decode - #276
Open
thedavidmeister wants to merge 1 commit into
Open
Conversation
RFC 8949 §5.6 makes a map with duplicate keys invalid. The visitor overwrote on a repeated key, and cbor_decode never saw the repeat at all because serde_cbor::Value collapses a map into a BTreeMap before the visitor runs. Closes #191 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Warning Review limit reachedNext included review available in 4 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This was referenced Aug 25, 2026
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.
Closes #191.
The defect
RainMetaDocumentV1Item'sDeserializevisitor assigned each recognised cbormap key with a plain overwrite, so a map repeating a key decoded successfully
with last-key-wins. RFC 8949 §5.6 makes such a map invalid, and two
byte-distinct documents decoding to the same item matters wherever the keccak
hash of the encoded bytes is the identity (metaboard subjects) while consumers
act on the decoded item.
The issue's repro reproduced on
main(45ca96c) exactly as filed:Why a guard in the visitor was not the whole fix
cbor_decodedid not decode into the item directly — it deserialized eachelement into a
serde_cbor::Valuefirst and then ranfrom_value.serde_cbor::Value::Mapis aBTreeMap, so the duplicate was collapsed tolast-wins before the visitor ever ran. Probed on the repro bytes:
So a duplicate-key guard in the visitor with the
Valuehop still in placeleaves
cbor_decodereturningOk— measured below, not assumed.The change
cbor_decodedeserializesRainMetaDocumentV1Itemstraight off theserde_cbor::Deserializerstream instead of throughValue+from_value.serde_cborforwardsdeserialize_maptodeserialize_any/parse_value, sothis is the same parse path the
Valuehop used (transparent tag handlingincluded) — it just no longer discards the duplicate before the visitor sees
it, and it drops an intermediate allocation.
set_oncehelper that returnsserde::de::Error::duplicate_fieldon a second sighting of any of keys0–4or the
OaSchemamagic key.The error surface is unchanged in kind: the rejection arrives as
Error::SerdeCborError, the same variant the missing-key and unknown-magicrejections already use.
cargo test -p rain-metadata(lib 310, doc 2, integration 15) green,cargo fmt --all --checkandcargo clippy -p rain-metadata --all-targets -D warningsclean, pre-commit hooks pass on the changed file. The repo-wide suite was not
run locally — this machine is running many concurrent agents.
QA
meta::tests::test_cbor_decode_duplicate_payload_key_errors,meta::tests::test_cbor_decode_duplicate_any_key_errors— each fails on base(the issue's repro bytes were run against unmodified
main45ca96c and printedRESULT: Ok([... payload: [2] ...]), so theunwrap_err()both tests startfrom panics there; confirmed again by mutation 2 below, which restores the base
decode path byte for byte and fails both tests).
crates/cli/src/meta/mod.rsset_onceguard:if slot.is_some()→if false && slot.is_some()(duplicate key silently overwrites again) →killed by
test_cbor_decode_duplicate_payload_key_errorsANDtest_cbor_decode_duplicate_any_key_errors(0 passed; 2 failed).crates/cli/src/meta/mod.rscbor_decodeloop:Self::deserialize(&mut deserializer)→ base'sserde_cbor::Value::deserialize+from_value(
set_onceleft intact, so the visitor guard is present but unreachable) →killed by the same two tests (
0 passed; 2 failed). This is the mutation thatproves the fix needs both halves; the other 50
meta::testspass under it.it is not valid") for the verdict, and the metadata-v1 header alias table for
which keys are recognised (0–4 plus the
OaSchemamagic). Every test input ishandwritten cbor — map header
0xa0 | n, byte string0x41, text string0x60 | len,0x1b+ 8 big-endian magic bytes — assembled byhandwritten_entries/handwritten_magic/handwritten_textand never bycbor_encode, which cannot emit a repeated key at all. Expected decode resultsare named literally (
payload [0x01],DotrainV1,ContentType::Cbor,ContentEncoding::Identity,ContentLanguage::En,schema "hi").accept last-wins? Answered as reject, on the RFC 8949 §5.6 authority the issue
itself names as the intent oracle; the "flagged rather than adjudicated" triage
note is therefore adjudicated here rather than deferred, and is the one call in
this PR worth a reviewer's disagreement. Covered for every recognised key, not
just the key 0 in the repro: each of
0,1,2,3,4and theOaSchemamagic key is repeated in turn, once with a differing value and once with an
identical value (§5.6 does not care whether the values agree). The same map
without any repeat is asserted to decode into the expected item first, so
"everything errors now" cannot pass these tests.
Overlap with other in-flight work
#186, #188, #190 and #203 all propose changes to the same
cbor_decoderegion,so they will conflict textually with this one and want rebasing rather than
merging blind:
Items missing mandatory cbor keys 0/1 abort the whole decode; spec says they MUST be treated as unexpected and dropped/ignored #188 (drop items missing keys 0/1) both change where a per-item error goes.
With the
Valuehop removed, that decision now lives at the singleSelf::deserializecall site instead of being split across theValueparseand
from_value, which should make either change simpler, not harder.if metas.is_empty() || ...block directly below the loop this PR edits. ThisPR changes neither that guard nor the reachability of its arms:
trackandmetasare still pushed together on the same success path.above the loop, untouched here.
magic.rsonly — no overlap.rainix-rs / static / rs-staticfails repo-wide on an unrelated rainix hook bug.