Skip to content

phil/svg-sanitizer

alpha

Let people upload and paste SVG images without letting them run code, load what they should not, or embed another document.

phil/svg-sanitizer reads an SVG image as XML — without network, without a document type, refusing entity declarations — and writes it anew, element by element and attribute by attribute, from allow lists. Scripts, foreignObject, frames and the other elements that run code or embed a document are left out with their content; event handlers are dropped; every href must point to a fragment of the image itself, or be a raster image in a data: address on <image>, or a link of an allowed scheme on <a>; style attributes, the <style> elements a policy allows and the presentation attributes that hold more than names, numbers and colours go through phil/css-sanitizer. Limits of size, depth, number of elements and attributes per element refuse an image whole before it costs anything. What is removed is listed, the first hundred removals with their reason and line, for the author to read.

It is for any PHP application that accepts SVG images from people: an import of word processing documents, a media library, an avatar or a logo upload, a block editor whose authors paste drawings. The output is safe to serve as an .svg file and, under the default policy — which leaves <style> out and prefixes every id with svg- —, to write inline in a page that other people read.

It is a plain PHP library built on PHP's dom and libxml extensions, ext-mbstring and phil/css-sanitizer. Nothing is filtered by regular expression on text: the image is parsed, and the output is built from an empty document, so that whatever the policy does not name never reaches it.

The design is small: SvgSanitizer sanitizes against a Policy and returns a Sanitized result — the SVG and the Removals, each with its RemovalReason. With symfony/validator installed, Validator\SafeSvg turns the removals into violations.

Status

PartState
XML read without network, document type nor entities; processing instructions and comments left outImplemented and tested
Allow lists of elements and attributes, SVG and XLink names as written; elements of other namespaces left outImplemented and tested
Elements that run code or embed a document, event handlers and xml:base never allowedImplemented and tested
References: fragments, raster images in data: addresses, links of allowed schemes; scheme tricks caughtImplemented and tested
style attributes, <style> elements and presentation attributes through phil/css-sanitizerImplemented and tested
Safe inline by default: <style> left out, every id and the fragments that point to it prefixedImplemented and tested
<style> kept by withStyleElements(true), every rule held under the image's rootImplemented and tested
SMIL animation under the policy, never of a reference, a style or an event handlerImplemented and tested
Limits of size, depth, number of elements and attributes per element; the first 100 removals reported with their reason and line, the others countedImplemented and tested
Symfony validator constraint SafeSvgImplemented and tested

Documentation

DocumentWhat it covers
InstallationRequirements and installing.
SanitizingSanitizing an image, the result, the removals and their reasons.
PolicyWhat the default policy allows, every with…(), and the limits.
Referenceshref, url(), links and data: addresses.
ThreatsWhat the sanitizer protects from, how, and what stays the application's.
ValidatorThe SafeSvg constraint.
Extension pointsThe policy, the CSS policy and its rewriter, the result.
ExceptionsSvgSanitizerException and InvalidPolicy.
TestingThe library's tests and mutation bar, and how to test code that sanitizes SVG.
ReferenceEvery type of the library, with its role.

Quick start

composer require phil/svg-sanitizer
use Phil\SvgSanitizer\SvgSanitizer;

$sanitized = new SvgSanitizer()->sanitize(<<<'SVG'
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10" onload="alert(1)">
      <script>alert(2)</script>
      <use href="https://example.com/sprite.svg#icon"/>
      <rect width="10" height="10" fill="url(#paper)" style="fill: red; position: fixed"/>
    </svg>
    SVG);

echo $sanitized->svg;
// <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10">
//
//   <use/>
//   <rect width="10" height="10" fill="url(#svg-paper)" style="fill: red"/>
// </svg>

foreach ($sanitized->removals as $removal) {
    printf("line %d: %s (%s)\n", $removal->line, $removal->reason->describe(), $removal->subject);
}
// line 1: an event handler (onload)
// line 2: an element that runs code or embeds another document (script)
// line 3: a reference to something outside the image (https://example.com/sprite.svg#icon)
// line 4: CSS that is not allowed (style: position)

Requirements

PHP 8.5 or later, with the dom, libxml and mbstring extensions; phil/css-sanitizer; symfony/validator for the SafeSvg constraint.

Contributing

Contributions are welcome on the PHIL monorepo: read the contribution guide and the code of conduct. Report a vulnerability privately, as the security policy explains, never in a public issue.

License

MIT, © Philippe Gamache, doing business as Le Gnome Archiviste; a Parler Haut Interagir Librement project. See LICENSE.