MED-39Recommended
Turn SVG uploads into plain images and refuse HTML
Leave HTML, XHTML, XML and SVG off every upload kind that other people view. Where people must upload a vector logo, convert the SVG to PNG on the server and serve only the PNG.
How to do it
An SVG file is a document, not a picture. It can hold scripts, event handlers, links to other files and external entities. A browser that opens it directly runs all of that. An HTML upload is a whole web page under your name, which also makes it a ready phishing page. Converting to PNG in a sandbox, as Run media tools in a sandbox with no network says, drops every script. If the SVG itself must be kept, clean it with an SVG allowlist sanitiser. Serve it only from the domain in Serve user files from their own domain. Detect the type from the bytes, not the name or the declared type.
Examples
JavaScript example
import sharp from 'sharp';
import { fileTypeFromBuffer } from 'file-type';
const LOGO_WIDTH = 512;
const VIEWABLE_TYPES = new Set(['image/png', 'image/jpeg', 'image/webp']);
export async function acceptLogo(bytes) {
const detected = await fileTypeFromBuffer(bytes);
if (detected?.mime === 'application/xml' || bytes.subarray(0, 512).toString().includes('<svg')) {
return sharp(bytes, { limitInputPixels: 25_000_000 }).resize(LOGO_WIDTH).png().toBuffer();
}
if (!VIEWABLE_TYPES.has(detected?.mime)) throw new UnsupportedMediaType(detected?.mime);
return bytes;
}
Python example
import cairosvg
import magic
LOGO_WIDTH = 512
VIEWABLE_TYPES = {"image/png", "image/jpeg", "image/webp"}
def accept_logo(data: bytes) -> bytes:
detected = magic.from_buffer(data, mime=True)
if detected == "image/svg+xml":
return cairosvg.svg2png(bytestring=data, output_width=LOGO_WIDTH, unsafe=False)
if detected not in VIEWABLE_TYPES:
raise UnsupportedMediaType(detected)
return data
How to check
Upload logo.svg holding <script> and onload= to the test copy. The stored file is a PNG, or an SVG with no script, handler or external reference. Upload page.html renamed to photo.jpg, and it is refused with 415.
Sources
Related rules
- MED-14 Run media tools in a sandbox with no network in Files and media
- MED-3 Serve user files from their own domain in Files and media
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/files-and-media/uploads/svg-and-html-uploads">Agent Wranglers files and media playbook</a>. In code you write, add a comment next to it with the same link.