IntraVox Security - SVG Upload Protection¶
Overview¶
IntraVox supports SVG uploads with comprehensive sanitization to prevent security attacks. This document explains the security measures implemented to safely handle SVG files.
Why SVG Files Need Sanitization¶
SVG (Scalable Vector Graphics) files are XML-based and can contain:
- JavaScript code via <script> tags
- Event handlers (onclick, onload, etc.)
- External entity references (XXE attacks)
- HTML injection via <foreignObject> elements
- Remote resource loading (privacy/tracking concerns)
Without proper sanitization, malicious SVG files could execute JavaScript in users' browsers, leading to XSS (Cross-Site Scripting) attacks.
Attack Vectors Prevented¶
| Attack Vector | Risk Level | How We Prevent It |
|---|---|---|
XSS via <script> tags |
Critical | Removed by svg-sanitize library |
| Event handlers | Critical | Stripped (onclick, onload, onerror, etc.) |
| XXE attacks | High | DOCTYPE declarations rejected |
HTML injection via <foreignObject> |
High | foreignObject elements removed |
| Remote resource loading | Medium | External references stripped |
Implementation¶
Server-Side Sanitization¶
Library: enshrined/svg-sanitize v0.20+
- Industry-standard PHP library
- Used by WordPress, Drupal, and other major platforms
- MIT licensed, actively maintained
Process:
1. File upload received
2. MIME type validated via PHP finfo_file() (not file extension)
3. SVG content sanitized through library
4. Additional DOCTYPE check (XXE prevention)
5. Sanitized content stored to disk
Code Location:
- lib/Service/PageService.php - sanitizeSVG() method (lines 3632-3662)
- Integration in uploadMedia() (line 1692-1698)
- Integration in uploadMediaWithOriginalName() (line 3780-3786)
Defense-in-Depth Layers¶
- MIME Type Validation: Server-side detection using
finfo_file()prevents file extension spoofing - Content Sanitization: Removes malicious elements and attributes
- DOCTYPE Rejection: Blocks XML external entity (XXE) attacks
- img Tag Rendering: Frontend renders SVG via
<img>tag (scripts won't execute even if sanitization bypassed) - File Size Limits: Maximum 50MB prevents DoS attacks
- Permission Checks: Existing Nextcloud permission system respected
What Gets Removed¶
The sanitizer removes or neutralizes:
- <script> elements
- Event handler attributes (onclick, onmouseover, onerror, onload, etc.)
- <foreignObject> elements
- <iframe> elements
- External stylesheets (<link>)
- Data URIs in certain contexts
- JavaScript protocol handlers (javascript:)
- DOCTYPE declarations
What Is Preserved¶
Safe SVG features are preserved:
- Basic shapes (rect, circle, path, polygon, etc.)
- Gradients and patterns
- Text elements
- Transformations and animations (SMIL, not JavaScript-based)
- Internal styles (style attribute, <style> elements with CSS only)
- Accessibility attributes (aria-*, role)
Security Best Practices¶
For Users¶
- Source Trust: Only upload SVG files from trusted sources
- Review Before Upload: Check SVG content if received from external parties
- Report Issues: If an SVG upload fails, contact your administrator (file may contain malicious content)
For Administrators¶
- Keep Updated: Regularly update IntraVox to get latest security patches
- Monitor Logs: Check Nextcloud logs for SVG sanitization errors
- Composer Dependencies: Keep
enshrined/svg-sanitizelibrary updated - File Permissions: Ensure proper Nextcloud ACLs for _resources folder
Testing¶
Valid SVG Examples¶
These will pass sanitization:
<!-- Simple shapes -->
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
<circle cx="50" cy="50" r="40" fill="blue"/>
</svg>
<!-- Gradients -->
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100">
<defs>
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:rgb(255,255,0);stop-opacity:1" />
<stop offset="100%" style="stop-color:rgb(255,0,0);stop-opacity:1" />
</linearGradient>
</defs>
<rect width="200" height="100" fill="url(#grad)" />
</svg>
Malicious SVG Examples (Will Be Rejected/Sanitized)¶
These will fail or be stripped:
<!-- Script injection (REMOVED) -->
<svg xmlns="http://www.w3.org/2000/svg">
<script>alert('XSS')</script>
</svg>
<!-- Event handler (REMOVED) -->
<svg xmlns="http://www.w3.org/2000/svg">
<circle onclick="alert('XSS')" cx="50" cy="50" r="40"/>
</svg>
<!-- XXE attack (REJECTED) -->
<!DOCTYPE svg [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<svg xmlns="http://www.w3.org/2000/svg">
<text>&xxe;</text>
</svg>
Technical Details¶
Sanitization Method¶
private function sanitizeSVG(string $svgContent): string {
try {
$sanitizer = new Sanitizer();
$sanitizer->removeRemoteReferences(true);
$cleanSvg = $sanitizer->sanitize($svgContent);
if ($cleanSvg === false || empty($cleanSvg)) {
throw new \Exception('SVG sanitization failed - file may contain malicious content');
}
// Additional security: reject DOCTYPE (XXE attack vector)
if (stripos($cleanSvg, '<!DOCTYPE') !== false) {
throw new \Exception('SVG contains DOCTYPE declaration (not allowed)');
}
return $cleanSvg;
} catch (\Exception $e) {
$this->logger->error('SVG sanitization error: ' . $e->getMessage());
throw new \Exception('Invalid SVG file: ' . $e->getMessage());
}
}
Error Handling¶
When SVG sanitization fails: 1. Error logged to Nextcloud log file 2. Exception thrown with clear message 3. Upload rejected (file not saved) 4. User receives error notification
Known Limitations¶
- Complex Animations: JavaScript-based SVG animations will be removed (use CSS/SMIL instead)
- Interactive Features: onclick handlers and similar interactivity stripped
- External Resources: Remote images, fonts, and stylesheets removed
- DOCTYPE: Any DOCTYPE declaration causes rejection (security measure)
Content Security Policy¶
IntraVox sets a strict CSP on all page responses:
No unsafe-eval or unsafe-inline directives are used. The Vue 3 runtime-only build and TipTap editor are fully CSP-compliant.
Feed Widget Security¶
Image Proxy HMAC¶
External feed images are served through a server-side proxy to bypass CSP restrictions. Each image URL is signed with an HMAC-SHA256 signature derived from the Nextcloud system secret. Only signed URLs are proxied — unsigned or tampered URLs are rejected with HTTP 403. The proxy sets Content-Security-Policy: default-src 'none', X-Content-Type-Options: nosniff, and Referrer-Policy: no-referrer on all responses.
SSRF Protection¶
All outbound HTTP requests from the feed system validate target URLs against private/reserved IP ranges using gethostbynamel() with FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE. This applies to RSS feeds, REST API connections, LMS API calls, SharePoint Graph API, and the image proxy.
Response Size Limit¶
API responses larger than 10 MB are rejected before JSON decoding to prevent out-of-memory conditions. This protects against misconfigured or malicious external APIs returning excessively large payloads.
Token Storage¶
API tokens and OAuth2 client secrets are encrypted at rest using Nextcloud's ICrypto service. Per-user OAuth2 tokens are stored in encrypted user preferences and automatically cleaned up when a user is deleted. Tokens are never included in feed connection exports, API responses, or log messages.
Rate Limiting¶
All mutating API endpoints are rate-limited using Nextcloud's built-in throttle attributes. See Scalability & Enterprise Readiness for the full rate limit table.
GDPR User Deletion¶
When a Nextcloud user is deleted, IntraVox's UserDeletedListener automatically removes all associated data: analytics records, page locks, feed tokens, and LMS OAuth tokens.
Audit Logging¶
Administrative operations (bulk delete/move, license changes, settings updates) are logged with the IntraVox Audit: prefix at info level for SIEM integration.
Compliance¶
This implementation follows: - OWASP Top 10: Protection against XSS, XXE, and injection attacks - CSP Best Practices: Strict CSP without unsafe-eval, img tag rendering prevents script execution - GDPR: Automated user data cleanup on account deletion - Industry Standards: Same approach used by WordPress, Drupal, GitHub
References¶
Version History¶
- v0.8.0 (2025-12-16): Initial SVG support with sanitization
- enshrined/svg-sanitize v0.20
- Server-side sanitization
- DOCTYPE rejection
- Multi-layer defense approach
Contact¶
For security concerns or bug reports: - GitHub Issues: https://github.com/nextcloud/intravox/issues
Last Updated: 2025-12-16 Document Version: 1.0