# A logo that can run code: why SVG uploads are an underestimated risk

> SVGs can contain scripts. Why uploaded logos are a real security risk — and how sanitising at upload time solves it reliably.

An SVG is not an image file in the usual sense but a text document that can contain scripts — which makes every uploaded logo executable code until proven otherwise. The SVG format explicitly provides a script element of its own; its purpose is to [add scripts to an SVG document](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/script). That is exactly why sanitising files like these belongs in the foundation, not in the hands of whoever happens to be uploading a logo.

I'm not writing this to spread alarm. I'm writing it because I've been seeing this gap for years in projects that are otherwise built cleanly — and because it's one of the few security problems you solve properly once and then never have to touch again.

## Why an SVG can do something a JPG can't

A JPG or PNG is a raster: a table of pixels. A browser can't turn that into anything other than colour on a screen. An SVG, by contrast, is a drawing instruction in text form — structured like a document, with elements, attributes and nesting. That structure is why SVGs stay razor-sharp at any size and weigh almost nothing. It is also the reason for the problem.

Because more than geometry fits into that structure. An SVG can contain a script element. It can carry event attributes that fire code on load or on click. It can embed foreign content and point to external addresses. And a logo with a script inside looks exactly like a logo without one. Neither the thumbnail nor the file manager shows the difference. It only becomes visible when someone opens the file in a text editor — and in practice, nobody does.

## Where the risk turns into an incident

It gets dangerous at one specific point: when the uploaded file is then served from your own address. If someone opens it directly, the script inside runs in the context of your domain — with everything the browser brings along there: a logged-in session, permissions, trust.

This is not a theoretical construct. It is a documented, steadily recurring class of vulnerability. One publicly documented example: in an open-source two-factor application, SVGs were accepted as a valid image type and served without protective headers when requested directly. An attacker could upload an SVG with JavaScript inside; anyone who then opened the file directly handed over their session. The case is on record as CVE-2024-52597, published on 20 November 2024, rated CVSS 6.1 and fixed in version 5.4.1 ([GitHub Security Advisory](https://github.com/Bubka/2FAuth/security/advisories/GHSA-q5p4-6q4v-gqg3)).

The sequence is always the same: allow the upload, store it unchecked, serve it from your own address. Three steps that all look harmless taken on their own.

## The fallacy of “but we only display it as an image”

A common objection goes like this: we only ever embed images as images, so nothing can happen. The first part is even true. When an SVG is embedded as an image — through an image element or as a background via a style rule — restrictions usually apply: JavaScript isn't executed and external resources aren't loaded. But those restrictions apply [explicitly only to the image context](https://developer.mozilla.org/en-US/docs/Web/SVG/Guides/SVG_as_an_Image). If the same file is opened directly or embedded as a document, they fall away — and the SVG behaves with its full range of functions, script execution included.

And that is exactly what the objection misses: how you embed the file on your site is your decision. Whether someone opens its address directly in a browser is not. The moment a file has a public address, calling it up directly is always possible.

## Who actually uploads SVGs

The answer explains why the topic is so persistent: almost everyone, and almost never with a check. The logo comes from the designer, the icon set from a library, the map graphic from the tourism board, the partner logo by email from the partner. These files pass through several hands before they land in a content system. Nobody in that chain has the job of looking inside the file — and nobody would have a tool for it either.

That's why “just pay attention” isn't a solution here. A rule that depends on the alertness of whoever is swapping out a logo under time pressure isn't a rule. It's a hope.

## The solution belongs at the door, not at the window

The approach that works is upload sanitisation: the file is checked and cleaned before it is stored at all — and what gets stored is the cleaned version, not the original. Four rules sit behind it:

- **Allow rather than forbid.** What gets through isn't “everything except scripts” but only the known drawing vocabulary: paths, shapes, colours, gradients, filters. Anything not on that list is dropped. Deny lists can be worked around; allow lists can't.
- **Scripts and embedded foreign content are stripped out.** The script element, and equally the mechanism that lets arbitrary HTML be smuggled into a graphic.
- **Event attributes are stripped out.** Anything that could fire code on load, on click or on error.
- **References may only point inwards.** References inside the same file are allowed — that's ordinary drawing technique. References pointing outwards are not, no matter how they're written. Because this is precisely where the tricks happen: unusual spellings, a different namespace, creative nesting.

The reason this has to happen at upload and not at display is banal and decisive: a file is stored once and served a thousand times. If the sanitising sits at the door, every later output path automatically inherits the clean file — the website, the cache, an export, an external delivery service. If it sits at the window, you have to retrofit it window by window, and you will forget one.

## Two details you only notice in practice

Building it shows that two unremarkable things decide here between success and aggravation.

First, the size value. If a file shrinks during sanitising, the size reported at the start is no longer correct. If it isn't carried over, the save fails at a completely different point — with an error message that has nothing to do with security. Side effects like this are why security measures get quietly dismantled again: not because they're wrong, but because they hurt in the wrong place.

Second, the list of permitted file types. A blanket rule along the lines of “all images” is convenient and dangerous, because it silently includes every future format. An explicit, finite list is less convenient and correct. Add to that a separate, tight size limit for graphics of this kind — a logo doesn't need 25 megabytes, and a tight limit incidentally caps how much can be hidden in such a file at all.

## Honest limits

Three things I don't want to claim.

No sanitising is a hundred per cent. The race between filters and inventive bypasses keeps running, which is why sanitising is one layer in a staged defence, not the whole defence.

It takes effect from the moment it is built in. Files that landed in the system beforehand don't become clean retroactively — anyone retrofitting a measure like this should take a separate look at the existing stock.

And it is an entirely legitimate decision not to allow SVG uploads for editors at all, and to have logos entered once by someone technically responsible. Less convenience, less attack surface. With us SVG is allowed and gets cleaned on save, because sharp logos at every screen size are a genuine everyday advantage — but that's a trade-off, not a law of nature.

## What you do with this as a decision-maker

You don't have to build this yourself. You only have to know what to ask. Three questions for whoever looks after your digital foundation:

1. Are uploaded files sanitised when they are saved — or stored unchanged?
2. Which file types are permitted at all: an explicit list or a blanket rule?
3. What happens to files that were uploaded before this rule existed?

If the answer to question 1 is “a bought-in extension takes care of that”, the next question is who maintains that extension and what happens when it stops being maintained. With us this kind of safeguard sits in the foundation itself — not as an add-on module you can switch on or forget. What else comes as standard is listed in the [feature overview](https://btlabs.dev/en/features); how that differs from an extension-based system, we have written up in the [comparison](https://btlabs.dev/en/comparison) of the two approaches.

If you're unsure how your current system handles uploads: [just get in touch](https://btlabs.dev/en/contact) — that's a question you settle in a conversation, not in a quote.

---
Source: https://btlabs.dev/en/posts/svg-uploads-underestimated-risk
Last-Modified: 2026-08-25T07:00:01.115Z
Languages: [de](https://btlabs.dev/llms/de/posts/svg-uploads-unterschaetztes-risiko) · [it](https://btlabs.dev/llms/it/posts/svg-caricati-rischio-sottovalutato)
See also: [llms.txt](https://btlabs.dev/llms.txt) · [ai.txt (Policy)](https://btlabs.dev/ai.txt) · [identity.json](https://btlabs.dev/identity.json)
