Appearance
Safe SVG uploads in Laravel — every file cleaned, the Safe SVG plugin's settings kept
A designer sends the new logo as an SVG: sharp on a phone and on a 4K screen, a few kilobytes. The media library says "this file type is not permitted", and it is right to. An SVG is a picture written as text, and that text may carry a script. Open the file at its address on your site and the script runs there, as the visitor: it reads their session, or loads a tracker from somewhere else. The designer never meant it; an icon pack downloaded from a free site, or an old export from a drawing program, may carry it all the same.
On WordPress, more than a million sites let SVG in through the Safe SVG plugin, which cleans each file as it is uploaded. kitloom/safe-svg does the same on a Laravel site, under the plugin's own names, so a site that moves from WordPress keeps who may upload SVG.
Ask the agent
With kitloom/ai connected, an AI agent puts files into the media library with upload_media, as the user whose key it holds:
- "Upload the logo at https://example.com/brand/logo.svg and make it the site's logo."
- "Here are our twelve category icons as SVG, add them to the library with sensible titles and alt texts."
Every SVG the agent uploads goes through the same cleaner as one uploaded in the admin. And the key's user decides: an agent working with an author's key, on a site where authors may not upload SVG, is told the type is not permitted, just as the author would be. How to connect one: Run your site from Claude or Cursor.
What is taken out
The cleaner keeps everything a drawing needs (shapes, paths, text, gradients, patterns, masks, filters, animation) and removes everything else:
| Taken out | Why |
|---|---|
<script>, onload, onclick and every other handler | they run code when the file is opened |
foreignObject | HTML inside the picture, with its own scripts |
<use>, <image>, fills and CSS pointing to another address | they fetch from elsewhere, a tracker or a script of someone else's |
<set> and animation of a link | they turn a clean link into a script after the file is drawn |
CSS @import, expression(), escapes | they load or run code, or hide what they do |
| Inkscape's and Illustrator's markup, RDF metadata, comments | nobody needs them on the site; the file gets smaller |
Pictures embedded inside the SVG (PNG, JPEG, GIF, WebP) and links to web or mail addresses stay. A file that is no SVG at all, declares XML entities (the "billion laughs" that hangs a server), or comes compressed as .svgz is refused, and the message says why.
It cleans every way a file comes in: Media → Library, the editor's Add Media, the picker of a featured image, Nova's media library, a file fetched from a URL, an agent, and a WordPress import (WXR). It is written for kitloom from the SVG specification: an allowlist, so whatever a later SVG version adds stays out until someone has looked at it.
Who may upload SVG
When the module is installed, every role that may upload files gets Upload SVG files: on a fresh site administrators, editors and authors. Safe SVG → Settings says who holds it; Users → Roles changes it, by the same rules as every other permission.
Allow large SVG files is the plugin's other setting: a detailed map or a technical drawing may be nested deeper than libxml reads by default. It costs memory, so it is off until you need it.
The data is the plugin's
The permission is the plugin's capability, safe_svg_upload_svg, and the large-files setting its option, safe_svg_large_svg. A WordPress site that moves to kitloom keeps the roles the plugin had given the right to, with nothing to import.
The SVG files that come along in wp-content/uploads were cleaned by the plugin when they were uploaded, but nothing says by which version. One command cleans them all again and reads their sizes:
bash
php artisan safe-svg:sanitize --dry-run # which files would change
php artisan safe-svg:sanitizeEach SVG also gets its width and height in the attachment's metadata, read from the file's width and height or its viewBox, as the plugin writes them, so a theme's <img> has its size and the page does not jump as it loads.
For a Laravel developer
The module is one implementation of a contract of kitloom/media: a Kitloom\Media\Contracts\UploadFilter tagged UploadFilter::TAG. It names its extensions, who may upload them, and how a file is cleaned; the library adds the extensions for the signed-in user and passes every such file through it before it reaches the disk. A type of your own that is safe only once cleaned plugs in the same way. A second guard for the server, a Content-Security-Policy for .svg in nginx, is in the guide.
Watch it
Try it
Open a sandbox in one click, with no sign-up: Filament or Nova. Every detail of the module is in the Safe SVG guide.