Skip to content

security: validate msgpack ext-type decoding#142

Merged
Meirtz merged 2 commits into
masterfrom
security/codec-ext-validation
Jul 3, 2026
Merged

security: validate msgpack ext-type decoding#142
Meirtz merged 2 commits into
masterfrom
security/codec-ext-validation

Conversation

@Meirtz

@Meirtz Meirtz commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

unpack() runs on payloads the peer shaped — including host-side unpacks of sandbox-emitted side-channel events (/trace, /log, plugin namespaces) — but the two msgpack extension types decoded with no validation. This PR makes ext decoding validated and bounded; the #116 story then covers both sandbox→host decode surfaces (pickle returns via #137's restricted unpickler, msgpack side channels here).

  • New ExtDecodeError (a ValueError): a malformed ext payload surfaces as this one typed error, never an arbitrary numpy/msgpack exception from mid-decode.
  • ndarray header is checked before the buffer is interpreted: dtype must parse, carry no objects, not be void/subarray, itemsize > 0; shape entries must be non-negative ints; prod(shape) * itemsize must equal the buffer length; final construction is wrapped as belt-and-braces (e.g. ndim above numpy's cap).
  • pydantic ext decode recursively re-enters the unpacker, so nesting is now depth-bounded (_MAX_EXT_DEPTH = 16; legitimate nesting — a model payload carrying an array or another dumped model — is shallow); inner msgpack errors are wrapped with the cause class visible.
  • Object/structured arrays are refused at encode with TypeError: dtype.str collapses a structured dtype to '|V<size>' (fields stripped), so such a payload could only ever decode to corrupted data.

Bug fix

Latent round-trip breakage: dtype.str for single-byte-order dtypes leads with the header separator ('|b1', '|i1', '|S5'), and the decoder split on the FIRST | — so bool/int8/bytes/unicode arrays failed to decode on master (ValueError: too many values to unpack). The shape now splits on the LAST separator; round-trips are covered per dtype class.

Review

Three-lens adversarial review (legit-traffic regressions / validation completeness / decode-state reentrancy) with two refuters per finding: 3 confirmed findings all fixed in the second commit (unwrapped frombuffer/reshape escapes, silent void decode, empty wrapped message); thread-safety concerns refuted (every unpack() call site is synchronous on the asyncio loop).

Testing

  • 27 new tests in tests/runtime/shared/test_codec.py (round-trip fidelity per dtype class incl. scalars and model-embedded arrays, plus refusal cases for each validation)
  • pytest -q tests plugins: 603 passed, 6 deselected
  • ruff check . clean; pyright 0 errors at --pythonversion 3.11 and 3.13

Closes #140.

🤖 Generated with Claude Code

Meirtz and others added 2 commits July 3, 2026 17:23
unpack() runs on peer-shaped payloads — including host-side unpacks of
sandbox-emitted side-channel events — so ext decoding is now validated
and bounded (#140): a malformed ext payload raises ExtDecodeError
instead of an arbitrary numpy/msgpack error from mid-decode, the
ndarray header is checked before the buffer is interpreted (dtype must
parse, carry no objects, and agree with the buffer size), and pydantic
ext nesting is depth-bounded so a payload cannot recurse the unpacker.

Also fixes a latent round-trip bug: dtype.str for single-byte-order
dtypes leads with the header separator ('|b1', '|i1', '|S5'), so
bool/int8/bytes arrays failed to decode — the shape now splits on the
LAST separator.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adversarial review found two payload classes that passed every check
and then raised a raw numpy ValueError from frombuffer/reshape (ndim
above numpy's cap; subarray dtypes that expand extra elements),
breaking the only-ExtDecodeError guarantee. Refuse subarray/void
dtypes explicitly, wrap the final construction as belt-and-braces,
and refuse object/structured arrays at encode — dtype.str collapses
a structured dtype to '|V<size>', so decoding it would return
silently field-stripped data. Wrapped messages use {exc!r} because
msgpack's FormatError stringifies to ''.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Meirtz Meirtz merged commit ecb0414 into master Jul 3, 2026
5 checks passed
@Meirtz Meirtz deleted the security/codec-ext-validation branch July 3, 2026 09:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

codec: validate msgpack ext-type decoding of sandbox-emitted side-channel payloads (#116 follow-up)

1 participant