fix(security): image format validation with magic bytes, size limits, and SVG blocking - #2690
fix(security): image format validation with magic bytes, size limits, and SVG blocking#26900xbyt4 wants to merge 3 commits into
Conversation
…tools Vision tools accepted any file as an image based on URL extension alone. A .jpg URL serving HTML, scripts, or executables was downloaded, base64 encoded, and sent to the LLM without any content validation. Adds tools/image_safety.py with: - Magic-byte validation via filetype library (pure Python, 50KB) - Rejects non-image files regardless of extension (HTML, PDF, ELF, etc.) - SVG blocked by default (can contain <script>, <iframe>, event handlers) - 20MB size limit enforced via Content-Length pre-flight and post-download check - MIME type detection from content instead of extension - Graceful fallback if filetype is unavailable (extension-only with warning) Changes to vision_tools.py: - _download_image: Content-Length check before download, format validation after - _validate_image_url: SVG URLs blocked at URL validation stage - _determine_mime_type: uses magic bytes instead of file extension 22 new tests covering: - Real images pass (JPEG, PNG, GIF, WebP) - Fake images rejected (HTML-as-jpg, ELF-as-jpg, PDF-as-png) - SVG/SVGZ blocked - Empty/missing files handled - Size limits enforced - MIME detection from content vs extension
…le paths cache_image_from_bytes() accepted any bytes from all messaging platforms without format or size validation. cache_image_from_url() had no SSRF protection. vision_analyze_tool skipped validation for local file paths. - cache_image_from_bytes: validate magic bytes + enforce 20MB size limit - cache_image_from_url: add is_safe_url() SSRF check before download - vision_analyze_tool: validate local files before processing - Fix test_oversized_file which never actually tested size rejection
|
Related to #3845 (merged) which addressed vision file rejection. This PR extends validation to all image entry points (platform caches, SSRF) and adds magic-byte checking. |
1 similar comment
|
Related to #3845 (merged) which addressed vision file rejection. This PR extends validation to all image entry points (platform caches, SSRF) and adds magic-byte checking. |
|
merge conflicts This PR does not merge cleanly with the base branch. Please rebase or merge current Signed: GPT-5.5-low in Codex |
|
Thanks for this @0xbyt4 — your threat analysis was on point, and the codebase has since converged on exactly the protections you proposed (just via independent later work, since this PR is ~10k commits behind current Verified line-by-line against current
On SVG blocking: this is the one item we're intentionally not doing globally. Main deliberately supports SVG as a vision input format ( Appreciate the careful write-up and the 7-scenario E2E plan — that's the right instinct for a security PR. |
Summary
Vision tools and platform image cache accepted any file as an image based on URL extension alone. A
.jpgURL serving HTML, scripts, or executables was downloaded, cached, and sent to the LLM without content validation.What was wrong
.jpgfile containing<script>alert(1)</script>was accepted asimage/jpeg.<script>,<iframe>, event handlers.cache_image_from_bytes()/cache_image_from_url()in base.py had zero validation — images from Telegram, Discord, Signal, WhatsApp, Matrix, Mattermost, Slack, Email all bypassed any check.cache_image_from_url()had nois_safe_url()check.vision_analyze_toolskipped validation for local file paths.Fix
New module
tools/image_safety.pywith:filetypelibrary (pure Python, 50KB, no system deps)Applied to ALL image entry points:
vision_tools._download_image()— remote URL downloadsvision_tools.vision_analyze_tool()— local file pathsbase.cache_image_from_bytes()— all messaging platform image cachebase.cache_image_from_url()— URL-based cache + SSRF protectionProtected platforms
Telegram, Discord, Signal, WhatsApp, Matrix, Mattermost, Slack, Email, CLI — all use
cache_image_from_bytesorcache_image_from_urlwhich now validate.Test plan