Running Umbraco 13? Security support ends 14 December. Fixed-price upgrade to 17: £8,999 + VAT See how it works →
simonantony
Umbraco

Allowing SVG and PDF uploads in Umbraco opens a security hole. We've built a free package that closes it.

SVGs and PDFs can both carry code. SafeMedia checks every one at upload, refuses anything unsafe, and finds the ones already sitting in your media library.

Turning on SVG uploads in Umbraco is a one-line config change, and it quietly lets anyone with an editor login store a file that runs code on your website. PDFs carry a quieter version of the same risk: a PDF can hold scripts and actions that run on the computer of whoever downloads it. Nothing in Umbraco checks what is inside either. We kept hitting this on Umbraco 17 upgrades, so we built SafeMedia: a free, open-source package that checks every SVG and PDF before Umbraco keeps it and refuses anything unsafe.

An SVG is a document, not a picture

A JPEG is a grid of pixels. An SVG is a text file that the browser reads and acts on, and it can carry script, click handlers, links that run code and whole embedded web pages.

Umbraco serves your media from your own domain. So if a hostile SVG is uploaded and someone opens it, the code runs as your website. If that someone is an administrator looking at the media library, it runs with their access.

Who could upload one? Any editor account. Or anyone who gets hold of one, through a reused password or a convincing phishing email. You don't need a malicious editor for this to go wrong.

A PDF can carry more than words

The risk with PDFs is different, and aimed at your visitors rather than your site. A browser's built-in PDF viewer mostly ignores scripts. Adobe Acrobat and other desktop readers don't: a PDF can run JavaScript, launch a program, send form data to another website or open a file hidden inside it. Your site becomes the delivery route, and people trust the download because it came from you.

SafeMedia refuses PDFs that contain scripts, actions other than ordinary navigation, attached files or the dynamic forms that can run code. Ordinary links to websites and email addresses are fine.

What SafeMedia does

You install it and that's it. There is nothing to configure to be secure.

Every SVG and PDF is checked at the moment it is uploaded. If it fails, nothing is stored and the editor sees why, in plain English: "This PDF was not saved because it contains JavaScript... Re-save it from the original application using Export as PDF."

Logos from Illustrator, Figma and Inkscape go straight through, and so do PDFs printed or exported from everyday tools. If a PDF has been edited many times and carries leftover data from earlier versions, SafeMedia checks that too: if it is clean, the file is saved and the editor gets a warning suggesting a fresh export, rather than a refusal.

Why it reads the file rather than searching it

This is the part developers will want to poke at. The obvious approach is to search the file for <script and javascript:. It's also the approach that gets bypassed, because the attacker chooses the encoding and the reader does the decoding. Each of these runs as script, and none of them contains the text javascript::

href="&#106;avascript:alert(1)"      entity
href="&#x6a;avascript:alert(1)"      hex entity
href="java&#x0A;script:alert(1)"     newline inside the scheme
href="java&#9;script:alert(1)"       tab inside the scheme

SafeMedia parses SVGs the way a browser does, so by the time it checks a value, the decoding has already happened. CSS has its own escape codes too, and those are decoded before checking as well.

PDFs need the same treatment. Most of a modern PDF is compressed, and names can be escaped, so /J#61vaScript means /JavaScript and a search of the raw file finds nothing. SafeMedia reads each PDF twice. First as a PDF reader would, following the document's own index, with everything unpacked and decoded. Then byte by byte, ignoring the index, so nothing can hide in parts of the file the index doesn't mention. Both checks must pass.

It also works from an allowlist. For SVGs, only the elements real artwork uses are allowed. For PDFs, only actions that move around the document or open a web page are allowed. Everything else is refused, including anything the formats add in future. It rejects rather than strips, because silently editing a logo or a brochure leaves an editor with a file that looks different and no idea why. And it fails closed: a file it can't read is refused.

The trap we found on the way

The natural place to hook this into Umbraco is the media save. Umbraco lets you cancel a save, the editor sees a rejection, and it looks like the job is done.

It isn't. In Umbraco 14 and later, the uploaded file is copied into media storage before that save event fires. Cancel the save and the editor sees "rejected", but the file is already on disk at a public /media/ address. We confirmed this on Umbraco 17.7.2 before shipping.

So SafeMedia refuses at the upload itself, before anything is written, and the save check is only a backstop. If you have written your own cancel-on-save check for any file type, it is worth testing whether the file really goes away.

Checking the files you already have

Installing SafeMedia protects new uploads. It can't do anything about files already in your media library, and those are still being served.

So it adds a health check. In the back office, go to Settings > Health Check > Security and run Unsafe files in the media library. It checks every SVG and PDF you have, including the recycle bin and any stray files in the media folder that no media item points to, and lists each one that would be refused today, with the reason. It only reports. Nothing is changed or deleted.

Free, and open to help

SafeMedia is MIT licensed and free. It's on NuGet, the Umbraco Marketplace and GitHub.

A package like this is trusted for its test suite, not its code, so there are two things we would genuinely like:

  • Real files it refuses. If a legitimate logo or document from one of your projects gets rejected, send it in. False positives are what get a security package uninstalled.

  • Ways around it. If you can get something past it, please report it privately through the repository's security policy. We will fix it and credit you.

Getting it on your site

SafeMedia needs Umbraco 17. Installing it is one command:

dotnet add package Simplepage.Umbraco.SafeMedia

If your site accepts SVG or PDF uploads, install it and run the health check. If you'd rather we did that for you, or you want someone to look over what is already in your media library, get in touch.