cbor2 review
cbor2 6.1.4 is a Rust-backed Python encoder and decoder for CBOR, the binary data format defined by RFC 8949. Its `dump`, `dumps`, `load`, and `loads` entry points feel familiar to users of the standard `json` module, but CBOR can carry byte strings, integer map keys, semantic tags, UUIDs, and Decimal values without private JSON wrappers. Version 6.1.4 rejects malformed bignums and incomplete indefinite maps, fixes a quadratic hash-collision case involving immutable maps, and corrects reference tracking for `bytearray`. Our installed wheel had no direct dependencies, included `py.typed`, and contained compiled `.so` files.
cbor2 6.1.4 installed in 0.2 seconds as one 2 MB package with no direct dependencies or audit findings in our sandbox, making it an easy choice when a protocol already specifies CBOR. Keep JSON for human-facing data, and avoid optional reference tags until every peer proves it can decode them.
We installed it
| Install | ✓ · 0.2s | 1 package on disk · 2 MB |
| Import | ✓ | import cbor2 in 0.07s · compiled extensions · py.typed · requires Python >=3.10 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does cbor2 install cleanly?
Yes. In a fresh container with an empty cache, pip install cbor2 finished in 0.2s, leaving 1 package and 2 MB on disk. pip-audit reported no known vulnerabilities.
What does cbor2 need to run?
Python >=3.10, and a platform wheel with compiled extensions. In our run import cbor2 succeeded in 0.07s, and the package ships py.typed for type checkers.
cbor2 or msgpack: which should you use?
msgpack: Use it when the protocol is MessagePack or its wider peer support matters more than CBOR semantic tags. cbor2 6.1.4 installed in 0.2 seconds as one 2 MB package with no direct dependencies or audit findings in our sandbox, making it an easy choice when a protocol already specifies CBOR.
When should you not use cbor2?
People routinely inspect or edit payloads with text tools. CBOR is binary, and the project's JSON diagnostic command explicitly loses type information.
Use it if
- An external specification already requires CBOR, as happens with COSE, CWT, WebAuthn, CoAP, and some device protocols.
- Messages must retain raw bytes, non-string keys, or registered semantic tags that would need an application-specific wrapper in JSON.
- You need canonical serialized bytes for a signature, digest, or byte-for-byte comparison.
- Every non-Python peer has been tested against the same tag, reference, and datetime choices.
- People routinely inspect or edit payloads with text tools. CBOR is binary, and the project's JSON diagnostic command explicitly loses type information.
- All clients already speak JSON. A fast JSON implementation such as orjson avoids a wire-format migration and its compatibility tests.
- You cannot place byte, nesting, and collection limits around untrusted input. The README says the decoder has not been tested against malicious data.
- Your target has no wheel and the build environment cannot install Rust. cbor2 6.x is implemented in Rust and source builds need its toolchain.
- Application hooks still depend on cbor2 5.x callback signatures or imports from `cbor2.encoder` and `cbor2.decoder`; version 6 changed or removed those interfaces.
- A peer decoder does not implement shared values or string references. Enabling either option puts additional tags on the wire.
Setup reality
We installed cbor2 6.1.4 in a fresh unprivileged Python 3.12 container. pip finished in 0.2 seconds, and the single installed package used 2 MB. import cbor2 took 0.07 seconds. It has zero direct dependencies, requires Python 3.10+, ships py.typed, and includes compiled .so extensions. pip-audit returned zero known vulnerabilities. The package metadata in our run did not state a license.
There are no credentials or configuration files. The format options are the configuration. Set canonical=True when signatures or hashes depend on stable bytes. value_sharing and string_referencing emit reference tags, so they belong in a documented protocol contract rather than a local optimization switch. A tag_hook should return unknown tags intact unless the application has an explicit rejection rule.
The 0.2-second install used a compatible wheel. If pip cannot find one for the interpreter and platform, it builds the Rust implementation from source; the README names Rust 1.93.0 as the tested toolchain. Use a binary-only install in containers where compilation is forbidden. python -m cbor2.tool helps inspect a capture, but its JSON output cannot round-trip byte strings or non-string mapping keys.
CBOR decoding does not construct executable Python objects as pickle does, yet a parser can still consume excessive memory or CPU. Limit the request body before calling loads, then choose max_depth, duplicate-key handling, and indefinite-item policy from the message schema. Release 6.1.4 closes malformed-map and bignum acceptance bugs plus a quadratic immutable-map collision, so older 6.x pins miss parser fixes that matter on hostile inputs.
Patterns
Round-trip bytes and mapping values round-trip-values
import cbor2
payload = cbor2.dumps({"id": 7, "blob": b"\x00\x01"})
record = cbor2.loads(payload)
assert record == {"id": 7, "blob": b"\x00\x01"}`dumps()` returns bytes, and a CBOR byte string decodes back to Python `bytes` without a base64 wrapper.
Serialize one item through a binary file read-write-stream
with open("record.cbor", "wb") as target:
cbor2.dump(record, target)
with open("record.cbor", "rb") as source:
restored = cbor2.load(source)Both calls expect binary file objects. `load()` consumes one CBOR item and leaves the stream at the following byte.
Define how a naive datetime is interpreted encode-naive-datetime
from datetime import datetime, timezone
import cbor2
created = datetime(2026, 8, 26, 12, 0)
payload = cbor2.dumps(created, timezone=timezone.utc)
restored = cbor2.loads(payload)The encoder will not infer a timezone for a naive datetime. `datetime_as_timestamp=True` chooses numeric epoch encoding instead.
Make equal mappings produce equal bytes produce-canonical-bytes
a = cbor2.dumps({"b": 1, "a": 2}, canonical=True)
b = cbor2.dumps({"a": 2, "b": 1}, canonical=True)
assert a == bCanonical mode controls map order and numeric representation. Use it when serialized bytes are signed or hashed.
Map a class to a private semantic tag encode-application-type
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def encode_point(encoder, point):
encoder.encode_semantic(4000, [point.x, point.y])
payload = cbor2.dumps(Point(3, 5), encoders={Point: encode_point})Private tag 4000 has meaning only by agreement. Document its value shape and coordinate the number with every peer.
Turn one semantic tag into an object decode-application-tag
def tag_hook(tag, immutable):
if tag.tag == 4000:
return Point(*tag.value)
return tag
point = cbor2.loads(payload, tag_hook=tag_hook)cbor2 6.x passes `(tag, immutable)` to this hook. Returning an unknown tag preserves its number and value for later handling.
Apply parser limits to an incoming message limit-untrusted-input
import io
raw = receive_body(max_bytes=1_000_000)
decoder = cbor2.CBORDecoder(
io.BytesIO(raw),
max_depth=32,
allow_indefinite=False,
allow_duplicate_keys=False,
)
value = decoder.decode()A 32-level depth limit does not cap total input. Reject an oversized body before constructing the decoder.
Consume concatenated CBOR values decode-item-sequence
decoder = cbor2.CBORDecoder(stream)
while True:
try:
yield decoder.decode()
except cbor2.CBORDecodeEOF:
break`CBORDecodeEOF` is the normal end of a sequence. A different decoding exception means the last value is incomplete or malformed.
Encode a cycle with shared-value tags preserve-cyclic-reference
node = {"name": "root"}
node["self"] = node
payload = cbor2.dumps(node, value_sharing=True)
copy = cbor2.loads(payload)
assert copy["self"] is copyShared-value mode adds reference tags to the data. Test this payload with each non-cbor2 decoder before adopting it on a shared wire.
Render a capture for command-line inspection inspect-cbor-file
python -m cbor2.tool --pretty message.cbor
# Input stored as base64
python -m cbor2.tool --decode message.cbor.b64The tool emits a JSON representation for diagnosis. The conversion loses CBOR types and is unsuitable for a round-trip migration.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| msgpack | PyPI | Use it when the protocol is MessagePack or its wider peer support matters more than CBOR semantic tags. |
| orjson | PyPI | Use it when payloads must remain JSON and encoding or decoding speed is the actual constraint. |
| msgspec | PyPI | Use it for typed structures with validation during JSON or MessagePack decoding. |
| cbor-diag | PyPI | Use it to work with CBOR diagnostic notation rather than as an application's main codec. |
More utils guides
lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

