fix(message): render received external SVG images#13142
Conversation
External images are fetched through the image proxy and served with a generic application/octet-stream content type. Browsers sniff raster formats in <img> tags, but they refuse to render SVG unless it is served as image/svg+xml, so SVG logos stayed blank and only the alt text and placeholder were shown. Detect SVG markup in the proxied response and serve it as a sanitised image/svg+xml document. A new SvgSanitizer service strips active content (scripts, event handlers, external/javascript references) and rejects DOCTYPE/entity declarations as XXE defence in depth, even though the <img> context does not execute scripts. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Joël de Jager <contact@joeldejager.nl>
|
Thanks for the contribution |
|
@coderabbitai review and scan for new vulnerabilities |
🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Get the list of changed files in this PR
gh pr view 13142 --repo nextcloud/mail --json files --jq '.files[].path'Length of output: 416 🏁 Script executed: #!/bin/bash
# Read the new SvgSanitizer and any modified proxy/controller files
fd -e php . lib/ --exec echo {} \; | grep -i 'svg\|proxy\|image' | sort
cat -n lib/Service/SvgSanitizer.php 2>/dev/null || echo "NOT FOUND"Length of output: 4248 🏁 Script executed: #!/bin/bash
# Find and read all relevant new/changed files
fd -e php . lib/ | xargs grep -l -i 'svg\|SvgSanitizer' 2>/dev/null | sort
# Also find proxy-related files
fd -e php . lib/ | xargs grep -l -i 'proxy\|imageproxy\|image_proxy' 2>/dev/null | sortLength of output: 730 🏁 Script executed: #!/bin/bash
cat -n lib/Controller/ProxyController.phpLength of output: 6202 🏁 Script executed: #!/bin/bash
cat -n tests/Unit/Service/SvgSanitizerTest.php
cat -n tests/Unit/Controller/ProxyControllerTest.phpLength of output: 14218
🔴 Security findings1.
|
| Concern | Status |
|---|---|
| HMAC validation before proxying | ✅ |
LIBXML_NONET prevents network fetch during parse |
✅ |
DOCTYPE / <!ENTITY pre-rejected by regex |
✅ |
on* event-handler attributes stripped |
✅ |
Namespace-aware element removal (local-name()) |
✅ |
External href / xlink:href stripped |
✅ |
Rate-limiting (#[UserRateLimit]) |
✅ |
LocalServerException caught (SSRF guard) |
✅ |
| Test coverage for sanitizer + controller | ✅ |
✅ Action performed
Review finished.
Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.
📝 WalkthroughWalkthroughA new ChangesSVG Sanitization in ProxyController
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (1)
lib/Controller/ProxyController.php (1)
137-146: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMove SVG detection heuristic out of the controller.
looksLikeSvg()is business logic and makesProxyControllerless of a thin HTTP handler. Consider moving this heuristic intoSvgSanitizer(or a dedicated detector service) and call it from here.As per coding guidelines,
lib/Controller/**/*.php: “Controllers must be thin HTTP handlers; business logic must live in services”.Source: Coding guidelines
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 26c52dd2-524b-4c89-9e7f-305d15c812e0
📒 Files selected for processing (4)
lib/Controller/ProxyController.phplib/Service/SvgSanitizer.phptests/Unit/Controller/ProxyControllerTest.phptests/Unit/Service/SvgSanitizerTest.php
|
Yes |
- Extend URL attribute stripping to cover src, action, formaction - Strip external CSS url() references from <style> elements and style="" attributes while preserving fragment references (url(#...)) - Reject SVG payloads exceeding 2 MiB before DOM parsing (DoS guard) - Fall back to blocked-image.png instead of serving empty body when sanitizer rejects an SVG document - Drop misleading <!-- branch from looksLikeSvg() heuristic - Move looksLikeSvg() from ProxyController into SvgSanitizer service Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Joël de Jager <contact@joeldejager.nl>
|
The recommended changes have been implemented. |
|
@coderabbitai re-review |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
cat -n lib/Service/SvgSanitizer.php
echo "---"
cat -n lib/Controller/ProxyController.phpLength of output: 11368
The implementation is clean and all security measures are in place. [approve] |
Summary
Received emails containing external SVG images did not render. The message showed only the broken-image placeholder and the image's alt text or name. External images are fetched through the image proxy, which served every response as
tag regardless of content type, but they refuse to render SVG unless it is served as
application/octet-stream. Browsers happily sniff raster formats (PNG/JPEG/GIF) out of animage/svg+xml, so SVG logos and graphics stayed blank.Changes
image/svg+xmlcontent type so browsers render it.SvgSanitizerservice that strips active content (scripts, event handlers, external/javascript: references) and rejects DOCTYPE/entity declarations, as defence in depth against XXE and stored XSS even though theThe first screenshot shows the issue; the second screenshot shows it has been resolved.
Assisted-by: Claude:claude-opus-4-8
Summary by CodeRabbit
Release Notes
New Features
Tests