Skip to content

Safe SVG ​

SVG uploads to the media library, cleaned of scripts and outside references.

kitloom/safe-svg · admins: kitloom/safe-svg-filament, kitloom/safe-svg-nova

Video lessons ​

Recorded in the Filament admin; the Nova admin works the same way.

SVG files in the media library: logos, icons and drawings that stay sharp at any size. Without this module the library refuses .svg, and for a reason — an SVG is a small program as much as a picture. Opened at its own address on the site it may run a script as the visitor, read their session, or quietly load something from elsewhere. kitloom/safe-svg takes SVG uploads from the roles you choose and cleans every file on the way in, as the Safe SVG plugin does in WordPress, with the same option and capability names, so a site that comes from WordPress keeps its settings.

Who may upload SVG ​

Safe SVG settings

The roles with the permission Upload SVG files (safe_svg_upload_svg, the plugin's capability). When the module is first installed, every role that may upload files gets it: on a fresh site that is Administrator, Editor and Author. A site that comes from WordPress keeps the roles the plugin had given it.

Change it on Users → Roles in either admin, the way you change any other permission. Safe SVG → Settings shows who holds it now.

Users without it get the usual "this file type is not permitted" message. The file never reaches the server's disk.

What is cleaned ​

Upload SVG files safely

Every SVG goes through the cleaner, whoever uploads it: from the media library, the editor's Add Media, an AI agent, a file fetched from a URL, or a WordPress import. What it keeps is a list of everything a drawing needs (shapes, paths, text, gradients, patterns, masks, filters, animation). Anything not on that list is removed:

  • scripts, event handlers (onclick, onload and the rest) and foreignObject, which carries HTML inside the picture;
  • elements and attributes of other programs (Inkscape's, Illustrator's, RDF metadata) and comments;
  • references to anything outside the file: a <use> or <image> of another address, an outside url() in a fill or in CSS, @import. A picture inside the SVG stays when it is embedded in the file (PNG, JPEG, GIF or WebP as data:); links (<a>) to web or mail addresses stay too — a drawing may be a clickable map — and lead where the file says, so a visitor who clicks a picture on your site can be taken anywhere. Give Upload SVG files only to people you trust;
  • the animation that turns a link into a script (<set>, animating href);
  • CSS that runs code (expression(), behavior) or hides what it does in escapes.

The drawing looks the same. The file is written again as clean XML, so it is often a little smaller than the upload.

A file is refused rather than cleaned when it is not an SVG at all, declares XML entities (the "billion laughs" attack that hangs the server), or is compressed (.svgz, upload it unpacked). The error names the reason.

Large files ​

libxml, which reads the file, refuses a document nested deeper than 256 levels or with a text over 10 MB. A detailed map or an exported technical drawing may be one of these. To take such files, tick Safe SVG → Settings → Allow large SVG files (safe_svg_large_svg). Cleaning them takes more memory, so leave it off unless you need it.

Size on the page ​

An SVG has no sizes to cut, so the library stores its width and height instead. They come from the file's width and height in pixels, or else its viewBox. That is what the width/height of an <img> and the admins' previews use. Every image size (thumbnail, large, …) of an SVG is the file itself.

Files uploaded before ​

SVG files that came over with the uploads folder from WordPress, or were put in by hand, are not cleaned on their own. Run:

bash
php artisan safe-svg:sanitize --dry-run   # which files would change
php artisan safe-svg:sanitize             # clean them and read their sizes again

A file the cleaner refuses is named and left as it is: delete it from the library if you do not trust it.

One more guard on the server ​

The cleaner is the main defence. You can add a second one in the web server, so that even a file it missed could run nothing when opened at its address. For nginx:

nginx
location ~* \.svg$ {
    add_header Content-Security-Policy "default-src 'none'; style-src 'unsafe-inline'; img-src data:" always;
}

Configuration ​

config/kitloom-safe-svg.php (php artisan vendor:publish --tag=kitloom-safe-svg-config):

KeyWhat
enabled (SAFE_SVG)false: SVG is refused again, as without the module; files already uploaded stay
large (SAFE_SVG_LARGE)true: large files are taken whatever the setting says
filament.register, nova.registerfalse: no Safe SVG section in that admin; uploads are cleaned all the same

For the application ​

The module is a Kitloom\Media\Contracts\UploadFilter tagged UploadFilter::TAG. kitloom/media asks every tagged filter which extensions it adds for a user (AllowedUploads::extensions($actor)) and passes every file of those extensions through it before storing (AttachmentStore::put(), AttachmentStore::filter() for a writer that stores files itself). A list of your own in kitloom-media.extensions still has svg added for the roles that may upload it, and an svg you put in that list yourself is cleaned too.

kitloom — modules and themes for Laravel.
Contact · Pricing · Services · License · Refunds · Privacy · Community · YouTube · [email protected]