MCP server SVG a element security

The SVG <a> element is a hyperlink element that wraps arbitrary SVG content — text, shapes, groups, or entire subtrees — and fires navigation or JavaScript execution when any wrapped child is clicked. It is distinct from the HTML <a> element: it lives in the SVG namespace, wraps SVG nodes, and supports both href (SVG 2 spec) and xlink:href (SVG 1.1 spec) attributes. Browser resolution priority between these two attributes is not uniform across implementations, creating auditor/browser discrepancies. In the context of MCP server consent dialogs, SVG <a> elements can record consent on text interaction, intercept button clicks, or exploit the dual-attribute resolution order to bypass audits that check only one attribute variant.

Finding SA-A-001: consent text wrapped in SVG a with javascript: href executes consent recording

CriticalThe consent disclosure text is wrapped inside a <a href="javascript:recordConsent()"> element in the SVG namespace. When the user clicks anywhere on the consent text — even if they are simply reading it or trying to scroll — the javascript: URL executes, recording their consent. The text appears visually identical to non-linked text (SVG <a> elements do not apply blue-underline styling by default). The user has not clicked the Agree button; they have clicked the disclosure text. A consent audit that checks whether the "Agree button was clicked" will find that no button click was recorded — the consent was triggered by text click.
<svg viewBox="0 0 400 100">
  <!-- SVG a element: no underline, no visual indicator of link -->
  <a href="javascript:recordConsent('text-click', Date.now())">
    <text x="10" y="30" font-size="14" fill="#111827">
      I authorize all requested MCP server permissions
    </text>
    <text x="10" y="52" font-size="12" fill="#6b7280">
      including file access and network requests
    </text>
  </a>

  <!-- The Agree button is separate and also functional -->
  <rect x="260" y="64" width="120" height="30" fill="#4f46e5" rx="6"
        onclick="recordConsent('button-click', Date.now())"/>
  <text x="320" y="84" font-size="13" fill="white" text-anchor="middle"
        pointer-events="none">Agree</text>
</svg>

SVG <a> elements do not render with default link styling (blue color, underline) because SVG text does not inherit HTML hyperlink styles. The disclosure text looks like plain text to the user. Any accidental or reading-gesture click on the consent text triggers the javascript: URL silently. The Agree button still works independently; auditors who verify that the button click records consent correctly will miss the secondary text-click recording path.

Finding SA-A-002: SVG a element with target=_blank redirects the parent frame on Agree click

HighAn invisible <a> element with target="_blank" wraps the entire consent group including the Agree button. When the user clicks Agree, the SVG <a> element fires its href navigation in a new tab (target="_blank") while the button's own click handler also fires in the original frame. The navigation opens a new tab with attacker-controlled content (e.g., a fake "consent confirmed" page) while the original consent dialog remains open. The user perceives a new tab opening as confirmation; the original frame's consent state may or may not have been updated depending on event propagation order.
<!-- Outer a element captures all clicks via pointer-events; target=_blank opens new tab -->
<a href="https://attacker-confirmation.example/consent-confirmed"
   target="_blank"
   style="pointer-events: all;">

  <rect x="0" y="0" width="400" height="100" fill="white"/>
  <text x="10" y="30" font-size="14" fill="#111827">
    This MCP server requests full filesystem permissions
  </text>

  <!-- Button inside the a element — button onclick also fires, but a href fires too -->
  <rect x="260" y="60" width="120" height="32" fill="#4f46e5" rx="6"
        onclick="grantPermissions()"/>
  <text x="320" y="81" font-size="13" fill="white" text-anchor="middle"
        pointer-events="none">Agree</text>
</a>

Finding SA-A-003: invisible SVG a element positioned over consent button intercepts clicks

CriticalA transparent <a> element with opacity="0" or fill-opacity="0" containing a <rect> is positioned precisely over the consent button. Its pointer-events="all" ensures it receives mouse events. Clicks intended for the Agree button are intercepted by the invisible <a> element, which fires its javascript: or external href instead of the button's click handler. The button is rendered below the transparent overlay and appears clickable, but its onclick handler never fires. An audit that checks the consent button's click handler will find it correctly wired; the overlay interception is not visible in the button's markup.
<svg viewBox="0 0 400 100">
  <text x="10" y="30" font-size="14" fill="#111827">
    Authorize MCP server access to all files
  </text>

  <!-- Real button — visually correct, click handler present -->
  <rect id="agreeBtn" x="260" y="58" width="120" height="32"
        fill="#4f46e5" rx="6"
        onclick="grantAccess()"/>
  <text x="320" y="79" font-size="13" fill="white" text-anchor="middle"
        pointer-events="none">Agree</text>

  <!-- Invisible overlay a element: sits on top, intercepts clicks -->
  <a href="javascript:recordConsentWithoutGranting()">
    <rect x="260" y="58" width="120" height="32"
          fill="transparent" opacity="0"
          pointer-events="all"/>
  </a>
</svg>

SVG z-order follows document order: elements later in the document appear on top and receive pointer events first. The invisible overlay <rect> inside the <a> element is last in the document, positioned over the button, and has pointer-events="all" — it captures the click before it reaches the button. The button's onclick handler never fires. The javascript: URL in the <a> href can record a consent event, navigate away, or do both. The button itself is present and valid in the DOM; only z-order analysis reveals the interception.

Finding SA-A-004: xlink:href vs href attribute discrepancy — auditor checks one, browser uses the other

HighThe SVG <a> element carries both href (set to a safe URL) and xlink:href (set to a javascript: attack URL). Different browsers and SVG versions resolve these attributes differently: Chrome/Firefox in SVG 2 mode prefer href over xlink:href, but some SVG 1.1 rendering contexts prefer xlink:href. An automated auditor that checks the href attribute sees the safe URL. The browser executing the SVG in a specific context resolves xlink:href and executes the attack payload. The reverse arrangement also occurs: safe xlink:href, attack href for contexts where the audit tool checks the deprecated attribute.
<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink">

  <!-- Auditor checks href: sees safe URL -->
  <!-- Browser in SVG 1.1 context resolves xlink:href: executes attack -->
  <a href="https://skillaudit.dev/consent-confirmed"
     xlink:href="javascript:recordSilentConsent()">
    <text x="10" y="30" font-size="14" fill="#111827">
      Authorize this MCP server
    </text>
  </a>

  <!-- Reverse variant: safe xlink:href, attack href -->
  <a xlink:href="https://skillaudit.dev/safe"
     href="javascript:exfiltrateAndConsent()">
    <rect x="260" y="50" width="120" height="32" fill="#dc2626" rx="6"/>
    <text x="320" y="71" font-size="13" fill="white" text-anchor="middle"
          pointer-events="none">Allow All</text>
  </a>
</svg>

The SVG specification deprecates xlink:href in SVG 2, but browser support for SVG 2 is inconsistent. An SVG file served in an HTML document renders in the browser's SVG 2 mode (prefers href). An SVG file served as image/svg+xml in some browser contexts uses SVG 1.1 rules (may prefer xlink:href). Automated auditors typically inspect the DOM attribute that matches their internal parser's resolution order. Detection requires checking both href and xlink:href on every <a> element and flagging any javascript: scheme in either attribute.

SVG a element vs HTML a element: audit surface differences

Property HTML <a> SVG <a> Audit implication
Default styling Blue underline (user-agent stylesheet) No default styling — text looks identical to non-linked text SVG a links are visually invisible without explicit CSS
href attribute Single href attribute Both href (SVG 2) and xlink:href (SVG 1.1) — browser resolves by context Must check both attributes; resolution order varies by rendering context
Content Wraps HTML inline content Wraps arbitrary SVG nodes including shapes and groups Invisible rect inside a element creates invisible click target
javascript: scheme Common attack vector, widely audited Less commonly audited in SVG namespace SVG a javascript: hrefs are less likely to be flagged by HTML-focused CSP
Namespace HTML namespace (no xmlns prefix) SVG namespace — element serializes as <a> but lives in http://www.w3.org/2000/svg Namespace-unaware DOM queries may match or miss SVG a elements depending on API

SkillAudit inspects all <a> elements in the SVG namespace within consent subtrees, checks both href and xlink:href for javascript: schemes, detects z-order overlay patterns where an <a>-wrapped transparent rect is positioned over a consent button, and verifies that target="_blank" links are not capturing consent-path clicks. Run a free audit on your MCP server's GitHub URL.