🧨 Real-World Scenario
You're building a profile page. A user uploads their avatar — maybe even in .svg
format. Everything works smoothly. But then...
- Another user visits their profile.
- Their browser loads the avatar.
- Suddenly, JavaScript executes in the background.
- Session hijacked. CSRF triggered. Game over.
How?
Because you let someone upload an SVG — and forgot how dangerous it can be.
🕳️ What Is Stored XSS via File Upload?
Stored XSS happens when an attacker injects JavaScript into a system (e.g., via form or upload), and this code is stored on the server and later rendered in other users’ browsers.
With SVGs, it’s even sneakier:
<svg xmlns="http://www.w3.org/2000/svg">
<script>alert(document.cookie)</script>
</svg>
If your backend doesn’t sanitize or serve this file safely, it will happily deliver this payload to unsuspecting users.
⚛ Why React / Next.js Apps Are Not Immune
React protects you from XSS inside JSX, but not from files served by your backend.
That means:
- If your API accepts and serves SVGs as-is,
- And you render them via
<img src={user.avatarUrl} />
...
You're trusting the browser not to execute embedded scripts. Bad idea.
🔐 How to Prevent Stored XSS in Your React / Next.js App
1. ❌ Don't Allow SVG Uploads (Unless Absolutely Necessary)
SVGs support:
-
<script>
,onload
,onmouseover
-
<foreignObject>
(HTML inside SVG) - External scripts via
<use xlink:href="...">
If you don’t sanitize, disallow them entirely.
2. ✅ If You Allow SVGs, Serve Them Safely
Never render them inline. Always serve with headers like:
Content-Type: image/svg+xml
Content-Disposition: attachment; filename="avatar.svg"
This tells the browser: “Download this. Don’t render inline.”
In Next.js (API route or middleware):
res.setHeader('Content-Disposition', 'attachment');
Or: Convert the SVG to PNG server-side and serve that instead.
3. 🧽 Sanitize SVGs (If You Really Must)
Use tools like:
sanitize-svg
-
DOMPurify
(in server-side mode)
⚠️ Never trust the file extension. Inspect and clean content.
4. 🛡 Add a Strict Content Security Policy (CSP)
Add this via next.config.js
:
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: `
default-src 'self';
script-src 'self';
img-src 'self' data:;
object-src 'none';
base-uri 'none';
`.replace(/\s{2,}/g, ' ').trim(),
},
],
},
];
},
};
This prevents:
- Inline scripts,
- Loading SVGs via
<object>
, - Abuse of
<base>
tags.
🧠 Developer Checklist
✅ | What to Do |
---|---|
🔍 | Inspect file content, not just extensions |
📛 | Block dangerous MIME types or force download |
🧼 | Sanitize SVGs or disallow them |
🧱 | Add CSP headers |
🚫 | Never use dangerouslySetInnerHTML with user content |
🧵 TL;DR
SVGs aren’t "just images" — they’re XML with scripting capabilities.
If you're not validating uploads and locking down how they're served, you're one SVG away from stored XSS.
💬 Let’s Discuss
Have you dealt with SVG-related vulnerabilities in your frontend apps?
Do you allow SVGs in uploads, or ban them entirely?
Drop your thoughts, horror stories, or tips in the comments 👇
🙌 Follow for More
I'm writing more about:
- Frontend security for React/Next.js devs
- Real-world web exploit mitigation
- CSP, SSRF, XSS, and modern browser defences
Follow me here on LinkedIn
Top comments (0)