From c10adc57a22d38c86287d2ad6dc0ef7481474cb3 Mon Sep 17 00:00:00 2001 From: hotragn Date: Sat, 29 Aug 2026 03:23:52 -0400 Subject: [PATCH 1/2] [Bugfix][Multimodal] Validate base64 video payloads, matching image and audio VideoMediaIO.load_base64 was the only base64 loader in the tree decoding without validate=True. pybase64 then silently discards every character outside the base64 alphabet and re-packs what is left, so the decoder is handed a byte stream no client ever sent and the resulting error blames the video rather than the encoding. Pass validate=True, matching ImageMediaIO, AudioMediaIO and the VideoEmbeddingMediaIO loader in the same module. binascii.Error is a ValueError subclass, so it maps to 400 through the existing handler with no new exception type. Signed-off-by: hotragn --- tests/multimodal/media/test_video.py | 25 +++++++++++++++++++++++++ vllm/multimodal/media/video.py | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/tests/multimodal/media/test_video.py b/tests/multimodal/media/test_video.py index 79070ac50d5d..79d3b8287692 100644 --- a/tests/multimodal/media/test_video.py +++ b/tests/multimodal/media/test_video.py @@ -36,6 +36,13 @@ assert ASSETS_DIR.exists() +@VIDEO_LOADER_REGISTRY.register("assert_never_reached") +class AssertNeverReachedVideoLoader(VideoLoader): + @classmethod + def load_bytes(cls, data: bytes, **kwargs) -> npt.NDArray: + raise AssertionError("video decoder reached with undecodable base64") + + @VIDEO_LOADER_REGISTRY.register("assert_10_frames_1_fps") class Assert10Frames1FPSVideoLoader(VideoLoader): @classmethod @@ -369,6 +376,24 @@ def test_load_base64_jpeg_raises_on_zero_num_frames(): videoio.load_base64("video/jpeg", data) +def test_load_base64_rejects_malformed(): + """Malformed base64 must be rejected before the video decoder sees it. + + Lenient decoding silently drops the invalid characters and re-packs what + is left, so every following byte shifts and the decoder is handed a byte + stream no client ever sent. Every other base64 loader in the tree decodes + strictly, including VideoEmbeddingMediaIO in this same module, so the + error names the encoding rather than blaming the video. + """ + encoded = pybase64.b64encode(bytes(range(64)) * 2).decode("ascii") + malformed = encoded[:8] + "!!!@@@" + encoded[8:] + + videoio = VideoMediaIO(ImageMediaIO(), video_backend="assert_never_reached") + + with pytest.raises(ValueError): + videoio.load_base64("video/mp4", malformed) + + def test_pynvvideocodec_unrelated_error_propagates( monkeypatch: pytest.MonkeyPatch, ): diff --git a/vllm/multimodal/media/video.py b/vllm/multimodal/media/video.py index f1f5783b0e8a..7528331cdfd0 100644 --- a/vllm/multimodal/media/video.py +++ b/vllm/multimodal/media/video.py @@ -190,7 +190,7 @@ def load_base64( } return MediaWithBytes((frames, metadata), data.encode()) - return self.load_bytes(pybase64.b64decode(data)) + return self.load_bytes(pybase64.b64decode(data, validate=True)) def load_file( self, filepath: Path From e7c5e15a0a18d09606f8759c3657c6701409ad04 Mon Sep 17 00:00:00 2001 From: Isotr0py <2037008807@qq.com> Date: Mon, 14 Sep 2026 10:51:08 +0800 Subject: [PATCH 2/2] Apply batched suggestions from code review Co-authored-by: Isotr0py <2037008807@qq.com> Signed-off-by: Isotr0py <2037008807@qq.com> --- tests/multimodal/media/test_video.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/tests/multimodal/media/test_video.py b/tests/multimodal/media/test_video.py index 79d3b8287692..79070ac50d5d 100644 --- a/tests/multimodal/media/test_video.py +++ b/tests/multimodal/media/test_video.py @@ -36,13 +36,6 @@ assert ASSETS_DIR.exists() -@VIDEO_LOADER_REGISTRY.register("assert_never_reached") -class AssertNeverReachedVideoLoader(VideoLoader): - @classmethod - def load_bytes(cls, data: bytes, **kwargs) -> npt.NDArray: - raise AssertionError("video decoder reached with undecodable base64") - - @VIDEO_LOADER_REGISTRY.register("assert_10_frames_1_fps") class Assert10Frames1FPSVideoLoader(VideoLoader): @classmethod @@ -376,24 +369,6 @@ def test_load_base64_jpeg_raises_on_zero_num_frames(): videoio.load_base64("video/jpeg", data) -def test_load_base64_rejects_malformed(): - """Malformed base64 must be rejected before the video decoder sees it. - - Lenient decoding silently drops the invalid characters and re-packs what - is left, so every following byte shifts and the decoder is handed a byte - stream no client ever sent. Every other base64 loader in the tree decodes - strictly, including VideoEmbeddingMediaIO in this same module, so the - error names the encoding rather than blaming the video. - """ - encoded = pybase64.b64encode(bytes(range(64)) * 2).decode("ascii") - malformed = encoded[:8] + "!!!@@@" + encoded[8:] - - videoio = VideoMediaIO(ImageMediaIO(), video_backend="assert_never_reached") - - with pytest.raises(ValueError): - videoio.load_base64("video/mp4", malformed) - - def test_pynvvideocodec_unrelated_error_propagates( monkeypatch: pytest.MonkeyPatch, ):