Proof of concept for WordPress Core Trac #24251 (open since 2013).
Demonstrates that SVG uploads can be made safe using only WordPress Core
APIs — no external runtime libraries required.
Security pipeline (8 ordered layers)
- XML well-formedness — DOMDocument + LIBXML_NONET | LIBXML_NOENT
- Preamble strip — XML declaration, DOCTYPE, HTML comments, whitespace-
encoded control characters (java script: bypass) - Deny-first threat scanner — blocked tags (script, foreignObject, handler,
image, feImage), on* event handlers, javascript:/data:/vbscript: schemes,
SMIL attributeName=on*/style runtime injection, unknown/prefixed tags - Fragment-only href enforcement — reference elements (use, animate, set,
mpath, textPath, animateTransform, animateMotion); excluded - External url() rejection — filter in/in2 and presentation attributes
(fill, stroke, filter, clip-path, mask, marker-*) - Inline style sanitization — CSS property allowlist + blocked-value patterns
- wp_kses() pass — SVG tag+attribute allowlist with camelCase round-trip;
quoted-value masking ensures values like fill="viewBox" are never rewritten - Re-validation — sanitised output re-parsed; invalid result -> ''
Architecture
Single-responsibility pipeline stages:
XmlValidator, ThreatScanner, ReferenceGuard, StyleSanitizer, CaseBridge
coordinated by a thin Sanitizer orchestrator. Allowlist memoised; tag-name
lookup is O(1); camelCase round-trip skipped entirely when markup has no
camelCase attribute (~65 us for icon SVGs, ~166 us with camelCase attrs).
Public API
$safe = wp_kses_svg( $raw_svg ); // '' on any failure
Upload pipeline hooks into wp_handle_upload_prefilter, upload_mimes and
wp_check_filetype_and_ext. Capability gate: wp_kses_svg_upload_capability
filter (default: upload_files).
Test suite
143 tests · 4301 assertions · 0 failures · PHP 8.1-8.4
PHPStan clean · PHPCS/WPCS clean
17 SVG fixtures (clean + XSS/SSRF/XXE attack vectors)
CI
GitHub Actions: PHPUnit (8.1/8.2/8.3/8.4), PHPStan, PHPCS/WPCS/PHPCompat,
plugin header consistency, README.md sync check.