Appearance
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
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
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,onloadand the rest) andforeignObject, 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 outsideurl()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 asdata:); 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>, animatinghref); - 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 againA 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):
| Key | What |
|---|---|
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.register | false: 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.