fix(mcp): pad body_base64, which strict decoders were rejecting #117
No reviewers
Labels
No labels
blocked
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
waiting-on-julian
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
jlxq0/matrix-mcp!117
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "base64-padding"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #116
download_attachmentencoded withSTANDARD_NO_PADwhileDownloadAttachmentResult.body_base64documented the standard alphabet. Standard-alphabet base64 is padded, so Python'sbase64.b64decode, Rust'sSTANDARDengine and Go'sStdEncodingall reject it. Node'sBuffer.fromandatobaccept it, which is why whether the bug was visible at all depended on which client tried first.Padded rather than the doc reworded, for the reason in the issue: adding padding is backwards-compatible because lenient decoders take padded input, and the failure mode of leaving it was a caller seeing correct
content_type,size_bytesandfilenamealongside a decoder error, and concluding the file was corrupt or still encrypted — the diagnosissize_bytesexists to make possible.Acceptance, as the issue specifies it
an_attachment_body_decodes_with_a_strict_decoderdecodes withbase64::engine::general_purpose::STANDARD— notSTANDARD_NO_PAD— and asserts the decoded length equals whatsize_byteswould carry. No padding fix-up anywhere in the test.encode_attachmentback toSTANDARD_NO_PADlen 10, 3, 1002)The second control is the one worth reading. A payload whose length is a multiple of three encodes identically padded and unpadded, so a fixture of that length is blind to this fault — which is how it reached production. The fixture covers 3n, 3n+1 and 3n+2; the real payload that surfaced it was 1273922 bytes, 3n+2.
What the test cannot see, stated in its own comment: the call site choosing a different engine inline instead of calling
encode_attachment. There is one such site and it sits two lines fromsize_bytes.Also
AGENTS.mdgets the alphabet trap. This crate needs both engines — PKCE insetup.rsrequiresURL_SAFE_NO_PADper RFC 7636, attachments require paddedSTANDARD— so "use the other one" is not a safe default, and the 3n blindness is written down next to it.docs/api-reference.mdnow says padded, and says to comparesize_bytesagainst the decoded length.Gates
fmt, clippy
-D warnings(0 errors), 210 tests, on rustc 1.98.0 — which is now whatci.ymlpins, per #114.Cross-engine review (Codex), and what it changed
Three specific questions, one real finding acted on in
7469439.Confirmed, not taken on trust:
encode_attachmentis the only producer ofbody_base64; nothing in the repository consumes the field (thedocs/api-reference.mdexample is illustrative and never decoded, andsend_image_from_urltakes a URL and uploads raw bytes, neverbody_base64); and the two other base64 sites are PKCE insetup.rs, whereURL_SAFE_NO_PADis required by RFC 7636 and correct. The media-upload paths hand raw bytes to the SDK and make no alphabet choice at all.The finding: the test asserted the decoded length against a fixture length it chose itself, never against an emitted
size_bytes. The two agreed on the real path only because the same local was used twice at the call site — a convention, not a structure.attachment_resultnow takes onebytesand derives both fields from it, so they cannot describe different data, and the pairing is assertable without a homeserver.size_bytesfrombytes.len() + 1body_base64encoded inline withSTANDARD_NO_PADinside the helperThe second closes most of the blind spot the first test admitted to. What remains is narrower: bypassing
attachment_resultentirely at its single call site.A control I had claimed rather than run, now run. With the padding-length assertion deleted and
STANDARD_NO_PADrestored, the strict decode alone still fails withInvalidPadding. SoSTANDARDgenuinely rejects unpadded input and the decode is load-bearing independently of the% 4assertion — this is not the WAV test's situation, where two mechanisms enforced one property and either could be deleted unseen. The comment now says which of the two it is.Gates re-run: fmt, clippy
-D warnings(0 errors), 211 tests on 1.98.0.