Contents
Introduction
Scalable Vector Graphics (SVG) are widely used for icons, logos and illustrations because they are lightweight and scale without loss of quality. By default WordPress blocks SVG uploads because SVG is an XML-based format that can contain active content (scripts, external references, event handlers). Allowing raw SVG uploads without sanitization can create a serious security risk (cross-site scripting, data exfiltration, remote resource loading).
This article explains how to allow SVG uploads in WordPress in a safe, robust way. It covers: policy and capability decisions, server hardening, WordPress filters and hooks, recommended sanitization libraries, a pure-PHP fallback sanitizer, validation and error handling, testing, and deployment suggestions. Example code is provided you can drop into a custom plugin or mu-plugin.
High-level approach
- Only allow SVG uploads for trusted users (admins or editors by policy).
- Use a recognized SVG sanitization library to remove scripts, event attributes, external references, foreignObject, etc.
- Validate MIME type and file extension using WordPress native helpers.
- Sanitize the uploaded SVG file on upload time (server-side), before saving to the uploads directory.
- Harden server delivery: disable PHP execution in uploads, set Content-Security-Policy, send X-Content-Type-Options: nosniff.
- Fail closed: if sanitization fails, reject the upload with a clear error message.
Policy decisions (who can upload?)
Before code, decide which roles may upload SVG. Typical choices:
- Admins only (recommended for maximum safety).
- Editors and above (if editors are trusted).
- All authenticated users (not recommended unless additional review workflow exists).
The examples below default to allowing only users with the manage_options capability (site administrators). Adjust capability checks to your site policy.
Server hardening (required)
Even with sanitization, follow these server hardening steps to reduce attack surface:
- Disable PHP execution in the uploads directory so an attacker cannot upload a PHP file and execute it.
- Set response headers: X-Content-Type-Options: nosniff and a restrictive Content-Security-Policy that limits where scripts and fonts can be loaded from.
- Restrict accessible file types at the server level (optional) and ensure correct MIME types are served.
- Scan uploads with malware/AV at the OS level if available.
Example .htaccess (for Apache)
# disable php execution in uploadsRewriteEngine On Require all denied # ensure SVG served with correct type and prevent sniffingHeader set X-Content-Type-Options nosniff
Example Nginx location snippet
# Disable execution and restrict PHP handlers in uploads location ~ /wp-content/uploads/..(phpphtmlphp3php4php5phps) { deny all return 403 } # Add header for svg files location ~ .svg { add_header X-Content-Type-Options nosniff try_files uri =404 }
Recommended sanitization libraries
Use an established library instead of rolling your own sanitizer if possible. Two commonly used PHP libraries:
- darylldoyle/svg-sanitizer (Composer package: enshrined/svg-sanitize). Actively maintained, built for sanitizing SVGs with sane defaults.
- php-svg-sanitizer and forks—evaluate the latest maintained fork before installing.
Using Composer in a WordPress environment: prefer building a small plugin with Composer dependencies included, or include a minimal vendor folder inside the plugin. Do not install global dependencies into WordPress core directories.
WordPress integration: the safe flow
- Hook into upload_mimes to allow the SVG mime type for trusted users only.
- Hook into wp_handle_upload_prefilter to detect SVG uploads, validate the file, read contents and run the sanitizer.
- If sanitizer returns a valid sanitized string, overwrite the temporary file with sanitized content and allow upload to continue.
- If sanitizer fails or the result is empty, reject the upload by setting file[error] with a helpful message.
- When serving files, ensure HTTP headers are set as described above.
Complete example plugin code (recommended approach using enshrined/svg-sanitize)
This example shows a minimal plugin file that integrates the svg-sanitize library (enshrined/svg-sanitize). Install the library using Composer in your plugin directory:
composer require enshrined/svg-sanitize
Then create a plugin PHP file (for example: safe-svg-uploader.php) and include the vendor autoload, then add the hooks shown below.
, etc.) // sanitizer->getConfig()->setAttr(allowedProtocols, [http, https, data]) clean = sanitizer->sanitize( contents ) if ( clean === false trim( clean ) === ) { file[error] = SVG sanitization failed. The file could not be cleaned. return file } // Optional: validate that the sanitized XML contains
Fallback: simple DOMDocument-based sanitizer (use only if you cannot install a library)
If you cannot install a maintained library, implement a conservative sanitizer based on DOMDocument. This is riskier and must be conservative: remove