Skip to content

fix(x): declare media_type matching the uploaded bytes#1670

Open
bradykirk wants to merge 1 commit into
gitroomhq:mainfrom
bradykirk:fix/x-media-type-upstream
Open

fix(x): declare media_type matching the uploaded bytes#1670
bradykirk wants to merge 1 commit into
gitroomhq:mainfrom
bradykirk:fix/x-media-type-upstream

Conversation

@bradykirk

Copy link
Copy Markdown

Problem

X's v2 /2/media/upload validates the declared media_type against the actual bytes and rejects any mismatch. XProvider.uploadMedia re-encoded every non-mp4 image to GIF (sharp(...).gif()) while still declaring the original type via lookup(m.path) (image/png, image/jpeg, ...). X rejected that mismatch, so all static-image posts failed with a generic bad_body error — only GIFs (whose bytes happened to match image/gif) succeeded.

This became fatal once uploads moved from the lenient v1.1 endpoint to v2 (which honors the declared media_type) in 8bb1fc82.

Fix

Extract prepareMedia(), which returns { buffer, media_type } together, keyed to what the bytes actually are:

  • mp4 → raw bytes, video/mp4
  • gif → passthrough, image/gif (preserves animation)
  • png / jpeg → keep original format (lossless, alpha preserved), matching media_type
  • anything else (webp, tiff, ...) → converted to jpeg

Images are downscaled to fit a 6000×6000 box (downscale-only) instead of a fixed width. This mirrors the approach already used in linkedin.provider.ts (prepareMediaBuffer).

Because media_type is now derived from the actual output format, it can never drift from the bytes.

Testing

Verified the invariant (declared media_type == actual buffer format) against real sharp output for JPEG, PNG (with transparency preserved), GIF, and WEBP inputs. The previous logic fails the JPEG/PNG/WEBP cases (bytes are gif, type declares otherwise); the new logic passes all.

X's v2 /2/media/upload validates the declared `media_type` against the actual
bytes and rejects any mismatch. uploadMedia re-encoded every non-mp4 image to
GIF (`sharp(...).gif()`) while still declaring the original type via
`lookup(m.path)` (image/png, image/jpeg, ...). The mismatch made X reject all
static-image posts with a generic error; only GIFs (whose bytes happened to
match) succeeded. It became fatal once uploads moved from the lenient v1.1
endpoint to v2, which honors the declared media_type.

Extract prepareMedia() which returns { buffer, media_type } together, keyed to
what the bytes actually are: mp4 -> video/mp4; gif -> image/gif (passthrough);
png/jpeg keep their original format (lossless, alpha preserved); anything else
is converted to jpeg. Images are downscaled to fit a 6000x6000 box instead of a
fixed width. This mirrors the approach already used in linkedin.provider.ts.
@postiz-agent

postiz-agent Bot commented Jul 2, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

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.

1 participant