Repository navigation
fix: reject malformed binary frames and decode empty metadata as {} - #19
Merged
Merged
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 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 |
HIVEMIND-WIRE-1 §4.2 states that codes 11 and 13-31 are unassigned and
"a receiver MUST reject a frame carrying one as malformed rather than
mapping it to any type." binary.py decode() read the 5-bit message-type
field and returned any value 0-31 unchecked, silently accepting a
reserved or unassigned code. §4.1 requires that "a receiver MUST decode
the block as a UTF-8 JSON object, and MUST treat a zero-length block as
an equivalent empty field set" — decode() instead returned the metadata
block as raw, unparsed bytes, so callers never saw {} for an empty block
or the parsed object for a populated one.
§4.1 also states: "A receiver MUST reject a frame whose frame-format
version it does not implement." decode() read the 8-bit frame-format
version field into protocol_version and returned it unchecked, so a
frame declaring version 2 (or any value other than the version 1 this
client implements) decoded and executed instead of being rejected.
decode() now raises a new MalformedBinaryFrame (a ValueError) for an
unassigned message-type code, an unimplemented frame-format version, a
metadata length that exceeds the remaining bytes, a metadata block that
is not valid JSON, or a truncated frame. A zero-length metadata block
decodes to {}; a non-empty one is parsed and returned as the JSON
object. The version check reads FRAME_FORMAT_VERSION, the same constant
encode() writes, rather than a literal. The one caller in client.py
that consumes decode() catches MalformedBinaryFrame, logs it, and drops
the frame without closing the connection.
Fail-before: decoding an unassigned code (13) against the unmodified
decode() returned {"msg_type": 13, ...} without raising; a frame
declaring frame-format version 2 (or 0, or 255) decoded and returned
protocol_version unchecked instead of raising. With only the version
check removed from decode() (constant kept for import), the three new
version tests failed: "AssertionError: MalformedBinaryFrame not
raised" for versions 0, 2 and 255. All three pass after the fix, with
the raised version named in the message.
Full suite: 88 passed (85 pre-existing plus 3 new), 0 skipped, 5
pre-existing deprecation warnings, 0 introduced.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
JarbasAl
force-pushed
the
fix/reject-malformed-frames
branch
from
September 10, 2026 15:16
96f19d1 to
605eb96
Compare
JarbasAl
added a commit
that referenced
this pull request
Sep 10, 2026
… legacy_hub
The client chose the pre-v3 password handshake automatically whenever the
hub could not offer Noise (no PSK, a v2-capped hub, max_protocol_version=2,
or no mutual pattern/suite), violating HIVEMIND-CRYPTO-1 §3 ("The handshake
is mandatory on every connection: there is no cleartext, pre-shared-key, or
password alternative and no protocol-version ladder to negotiate down") and
§5 ("A node MUST run the mandatory Noise handshake on every connection and
reject any peer that cannot complete it, with no cleartext or legacy
fallback").
A hub that cannot complete Noise is now rejected: no shake response is sent
and last_error names the violated clause. legacy_hub (default False) is the
only way to reach a legacy hub; setting it prints a loud warning at connect
time naming the pre-v3 handshake's lack of PAKE/forward secrecy and the
computed next-major removal version. max_protocol_version below 3 now
requires legacy_hub=True at construction time, since it can only ever reach
a hub through that same legacy path.
Fail-before: reverting the source (keeping the new tests) failed 6 of 6
rewritten tests, including a TypeError on the missing legacy_hub kwarg and
an AssertionError expecting rejection where the client instead sent a
plaintext shake. Restoring the fix: full suite 87 passed (baseline 85 at
PR #19's head, +2 net across replaced/added legacy-handshake tests).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
JarbasAl
marked this pull request as ready for review
September 10, 2026 20:20
JarbasAl
added a commit
that referenced
this pull request
Sep 10, 2026
… legacy_hub
The client chose the pre-v3 password handshake automatically whenever the
hub could not offer Noise (no PSK, a v2-capped hub, max_protocol_version=2,
or no mutual pattern/suite), violating HIVEMIND-CRYPTO-1 §3 ("The handshake
is mandatory on every connection: there is no cleartext, pre-shared-key, or
password alternative and no protocol-version ladder to negotiate down") and
§5 ("A node MUST run the mandatory Noise handshake on every connection and
reject any peer that cannot complete it, with no cleartext or legacy
fallback").
A hub that cannot complete Noise is now rejected: no shake response is sent
and last_error names the violated clause. legacy_hub (default False) is the
only way to reach a legacy hub; setting it prints a loud warning at connect
time naming the pre-v3 handshake's lack of PAKE/forward secrecy and the
computed next-major removal version. max_protocol_version below 3 now
requires legacy_hub=True at construction time, since it can only ever reach
a hub through that same legacy path.
Fail-before: reverting the source (keeping the new tests) failed 6 of 6
rewritten tests, including a TypeError on the missing legacy_hub kwarg and
an AssertionError expecting rejection where the client instead sent a
plaintext shake. Restoring the fix: full suite 87 passed (baseline 85 at
PR #19's head, +2 net across replaced/added legacy-handshake tests).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
JarbasAl
added a commit
that referenced
this pull request
Sep 10, 2026
… legacy_hub
The client chose the pre-v3 password handshake automatically whenever the
hub could not offer Noise (no PSK, a v2-capped hub, max_protocol_version=2,
or no mutual pattern/suite), violating HIVEMIND-CRYPTO-1 §3 ("The handshake
is mandatory on every connection: there is no cleartext, pre-shared-key, or
password alternative and no protocol-version ladder to negotiate down") and
§5 ("A node MUST run the mandatory Noise handshake on every connection and
reject any peer that cannot complete it, with no cleartext or legacy
fallback").
A hub that cannot complete Noise is now rejected: no shake response is sent
and last_error names the violated clause. legacy_hub (default False) is the
only way to reach a legacy hub; setting it prints a loud warning at connect
time naming the pre-v3 handshake's lack of PAKE/forward secrecy and the
computed next-major removal version. max_protocol_version below 3 now
requires legacy_hub=True at construction time, since it can only ever reach
a hub through that same legacy path.
Fail-before: reverting the source (keeping the new tests) failed 6 of 6
rewritten tests, including a TypeError on the missing legacy_hub kwarg and
an AssertionError expecting rejection where the client instead sent a
plaintext shake. Restoring the fix: full suite 87 passed (baseline 85 at
PR #19's head, +2 net across replaced/added legacy-handshake tests).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
JarbasAl
added a commit
that referenced
this pull request
Sep 10, 2026
… legacy_hub (#20) The client chose the pre-v3 password handshake automatically whenever the hub could not offer Noise (no PSK, a v2-capped hub, max_protocol_version=2, or no mutual pattern/suite), violating HIVEMIND-CRYPTO-1 §3 ("The handshake is mandatory on every connection: there is no cleartext, pre-shared-key, or password alternative and no protocol-version ladder to negotiate down") and §5 ("A node MUST run the mandatory Noise handshake on every connection and reject any peer that cannot complete it, with no cleartext or legacy fallback"). A hub that cannot complete Noise is now rejected: no shake response is sent and last_error names the violated clause. legacy_hub (default False) is the only way to reach a legacy hub; setting it prints a loud warning at connect time naming the pre-v3 handshake's lack of PAKE/forward secrecy and the computed next-major removal version. max_protocol_version below 3 now requires legacy_hub=True at construction time, since it can only ever reach a hub through that same legacy path. Fail-before: reverting the source (keeping the new tests) failed 6 of 6 rewritten tests, including a TypeError on the missing legacy_hub kwarg and an AssertionError expecting rejection where the client instead sent a plaintext shake. Restoring the fix: full suite 87 passed (baseline 85 at PR #19's head, +2 net across replaced/added legacy-handshake tests). Co-authored-by: JarbasAi <jarbasai@mailfence.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
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.
hivemind/binary.pydecode()read the 5-bit message-type field of a binary frame and returned whatever value it found (0-31) unvalidated, and returned the metadata block as raw, unparsed bytes that no caller ever decoded as JSON. This PR adds aMalformedBinaryFrameerror, raised for an unassigned message-type code, a metadata length that overruns the remaining bytes, non-JSON metadata, or a truncated frame, and changes the metadata field to decode to the parsed JSON object ({}for a zero-length block). The one caller inhivemind/client.pynow catches the new error, logs it, and drops the frame without closing the connection.HIVEMIND-WIRE-1 §4.2: "Code 11 and codes 13–31 are unassigned. A sender MUST NOT emit them, and a receiver MUST reject a frame carrying one as malformed rather than mapping it to any type." Assigned codes are 0–10 and 12.
HIVEMIND-WIRE-1 §4.1: "A receiver MUST decode the block as a UTF-8 JSON object, and MUST treat a zero-length block as an equivalent empty field set without attempting to parse it."
Verified by execution:
The 85 total is the pre-existing 78 plus 7 new tests (three unassigned-code rejections, an over-long metadata length, a truncated frame, a non-JSON metadata block, and a 200-iteration fuzz test asserting nothing but
MalformedBinaryFrameor a clean decode ever escapes). No test was skipped. The pre-existingTestLegacyFallbacksuite and the legacy password-handshake fallback inclient.pyare untouched; that fallback is a separate, tracked backcompat decision, not part of this change.