Escape and allowlist embedded image data URLs to prevent stored XSS#280
Escape and allowlist embedded image data URLs to prevent stored XSS#280evoskamp wants to merge 1 commit into
Conversation
The embedded-image path interpolated an image's media_type and base64 data straight into an <img src="..."> attribute with no escaping, allowlist, or base64 validation. These fields come from transcript JSON and can carry content that did not originate from the user (tool/MCP- returned images, fetched web content), so a crafted media_type such as `png"><script>...</script>` broke out of the src attribute and injected a live script that runs when the generated HTML is opened under file://. Add a shared _is_safe_image_source guard in image_export.py that allowlists the media type (png/jpeg/gif/webp, excluding scriptable SVG) and validates the base64, applied to both embedded and referenced modes. Escape the final src at both HTML <img> sinks. This mirrors the guards already present on the tool-result image path. The markdown output path () intentionally keeps the raw data URL but still benefits from the upstream allowlist and validation. Adds regression tests for the attribute-breakout media_type, SVG rejection, invalid base64, referenced-mode rejection, and end-to-end HTML-sink escaping. References: daaain#277 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q2uhgREMMmpYR6AMH65whA
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughImage export now restricts media types and validates base64 data for embedded and referenced images. HTML formatters escape generated image sources before rendering ChangesImage security hardening
Estimated code review effort: 2 (Simple) | ~10 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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 |
|
Thanks! Looks good & useful. Would you mind considering making |
What & why
The embedded-image rendering path interpolated an image's
media_typeand base64datastraight into an<img src="...">attribute with no HTML-escaping, media-type allowlist, or base64 validation. Those fields come from transcript JSON and can carry content that did not originate from the user (images returned by tools/MCP servers, fetched from web pages, IDE/file attachments), so a craftedmedia_typebreaks out of the attribute and injects a live<script>— stored XSS that runs when the generated HTML is opened in a browser underfile://(local-file read / exfiltration).embeddedis the default HTML image mode, so this is the normal path.Repro (before this PR)
A user-turn image block with
media_type = png"><script>alert(document.domain)</script>rendered as:The
">closes thesrcattribute and the<img>tag; the<script>executes on open. A"indatadoes the same.The fix
_is_safe_image_source()guard inimage_export.py: allowlistsmedia_typetoimage/{png,jpeg,gif,webp}(excludes scriptableimage/svg+xml) and validates the base64 withbase64.b64decode(validate=True). Applied to bothembeddedandreferencedmodes;image/svg+xmldropped from_get_extension.<img>sinks (html/assistant_formatters.py,html/renderer.py) now wrapsrcinescape_html.html/tool_formatters.py), which allowlisted, validated, and escaped — the user/assistant path had simply missed all three.The Markdown output path (
) intentionally keeps the raw data URL (escaping would be wrong there) but still gains the upstream allowlist + base64 validation.Tests
Adds regression coverage in
test/test_image_export.py:media_typerejected (embedded),<img>sink emits no"><scriptbreakout.Full unit suite green;
pyrightclean on changed files.Closes #277
Summary by CodeRabbit