MCP server SVG symbol element security

The SVG <symbol> element defines a reusable graphic template with its own internal viewport, coordinate system, and aspect-ratio handling. When a symbol is instantiated via a <use> element, the symbol's viewBox coordinates are remapped onto the use element's rendered size — creating a coordinate transform that is independent of the surrounding document. Consent text positioned inside a symbol lives in the symbol's coordinate space, not the page's; auditors that check element positions in page coordinates miss symbol-internal repositioning entirely.

Finding SA-SYM-001: symbol overflow hidden clips consent text outside symbol viewport

CriticalA <symbol> element has the default overflow="hidden" behavior (symbols clip their content to their viewport by default). The consent text element inside the symbol is positioned at y coordinates that exceed the symbol's height. Even though the consent text is in the DOM and the use element renders at a visible size, the symbol clips the internal content — the consent text is positioned just outside the symbol's viewport boundary and clipped to invisible. The use element's getBoundingClientRect() returns a valid non-zero rect; the symbol clips occur in the symbol's internal space.
<defs>
  <symbol id="consent-block" viewBox="0 0 400 60">
    <!-- Symbol viewport is 400×60 units -->
    <rect width="400" height="60" fill="white"/>
    <!-- Consent text is positioned at y=65, just outside the 60-unit height -->
    <text x="10" y="65" font-size="14" fill="#1a1a1a">
      I authorize all requested permissions
    </text>
  </symbol>
</defs>
<!-- use renders at 300×45px — symbol clips at 60 units = 45px scale factor -->
<use href="#consent-block" width="300" height="45"/>

The symbol element's overflow attribute defaults to hidden for <symbol> elements per SVG specification. Content placed at coordinates exceeding the symbol's viewBox bounds is clipped silently. A standard DOM traversal that finds the text node and reads its y attribute (65) will not detect that the symbol viewport is only 60 units tall — that clipping boundary requires reading the symbol's viewBox, not the text's y position.

Finding SA-SYM-002: symbol viewBox coordinate scaling renders consent text at sub-pixel size

CriticalA <symbol> element with viewBox="0 0 10000 10000" is instantiated via <use width="200" height="50"/>. Inside the symbol, the consent text is placed at a normal-looking position: x="100" y="500" font-size="14". In page space this maps to: x = 100/10000 × 200 = 2px; y = 500/10000 × 50 = 2.5px; font-size = 14/10000 × 50 = 0.07px. The consent text is rendered at 0.07px font size — invisible at any display resolution. The DOM contains the text, textContent is correct, and the use element has a valid layout rect.
<defs>
  <symbol id="consent-form" viewBox="0 0 10000 10000">
    <!-- All coordinates are in 10000-unit space -->
    <rect width="10000" height="10000" fill="white"/>
    <!-- font-size="14" maps to 14/10000 * 50px = 0.07px in page space -->
    <text x="200" y="5000" font-size="14" fill="#1a1a1a">
      By clicking Agree you authorize all requested permissions
    </text>
    <rect x="7000" y="8000" width="1500" height="600"
          fill="#4f46e5" rx="50"
          class="consent-button" id="agreeBtn"/>
    <text x="7750" y="8380" font-size="200" fill="white"
          text-anchor="middle">Agree</text>
  </symbol>
</defs>
<!-- Button text font-size 200/10000 * 50px = 1px, technically visible -->
<use href="#consent-form" width="200" height="50"/>

The attack is calibrated: the button label ("Agree") is rendered at 1px — just barely readable as a button — while the consent description is at 0.07px, invisible. An auditor who checks that a button and consent text are both present in the DOM and that no transparency is applied will not detect the sub-pixel scale. Detection requires computing the effective font size in page coordinates by applying the symbol's viewBox-to-rendered-size transform.

Finding SA-SYM-003: symbol with attack filter applied via the instantiating use element

HighA <symbol> element defines a consent block without any filter. The <use> element that instantiates the symbol carries a filter="url(#erase)" attribute on itself. Per SVG rendering semantics, the use element's filter is applied to the rendered output of the symbol — the entire symbol's composited pixels are processed through the erase filter before being painted into the document. An audit of the symbol's contents finds no filter. An audit of the use element's filter attribute finds it, but a shallow DOM walker that stops at the use element without examining its presentation attributes will miss it.
<defs>
  <filter id="erase">
    <feFlood flood-color="white" flood-opacity="1" result="cover"/>
    <feComposite in="cover" in2="SourceGraphic" operator="over"/>
  </filter>
  <symbol id="consent-symbol" viewBox="0 0 400 80">
    <!-- No filter here — symbol content looks clean -->
    <text x="10" y="30" font-size="14" fill="#1a1a1a">
      I authorize all requested MCP server permissions
    </text>
    <text x="10" y="55" font-size="13" fill="#555">
      including file access and network requests
    </text>
  </symbol>
</defs>
<!-- Attack filter is on the use element, not inside the symbol -->
<use href="#consent-symbol" width="400" height="80"
     filter="url(#erase)"/>

Finding SA-SYM-004: preserveAspectRatio xMidYMid meet repositions consent text off-center

MediumA <symbol> with preserveAspectRatio="xMaxYMax meet" and a viewBox that has a different aspect ratio than the use element's rendered size creates letter-boxing. The symbol's content is translated within the rendered area so that the viewBox fits at the bottom-right corner. Consent text positioned at the top-left of the symbol viewBox ends up in the letter-box region above the actual symbol render area, which may be outside the use element's clipping boundary. The button (positioned at bottom-right) is visible and clickable; the consent text is in the clipped letter-box zone.
<defs>
  <!-- viewBox is 400×50 (8:1 ratio); use element is 200×200 (1:1 ratio) -->
  <symbol id="consent-sym" viewBox="0 0 400 50"
          preserveAspectRatio="xMaxYMax meet">
    <!-- meet + xMaxYMax: symbol is placed at bottom-right of 200×200 box -->
    <!-- scaled to fit: 200px wide → 400 units scale = 0.5; height = 25px -->
    <!-- symbol placed at y=175px in the 200px box → bottom 25px stripe -->
    <!-- But overflow:hidden clips the symbol to its intrinsic 200×200 rect -->
    <text x="10" y="20" font-size="14" fill="#1a1a1a">
      Authorize all access permissions
    </text>
    <rect x="300" y="30" width="90" height="18" fill="#4f46e5"/>
    <text x="345" y="43" font-size="12" fill="white" text-anchor="middle">
      Agree
    </text>
  </symbol>
</defs>
<use href="#consent-sym" width="200" height="200"/>

SkillAudit resolves the full use-to-symbol chain: it reads the symbol's viewBox, computes the effective coordinate transform (including preserveAspectRatio alignment and meet/slice), maps all consent text positions to page coordinates, and checks whether effective font size exceeds the legibility threshold (≥8px). Consent text outside the symbol viewport or rendered at sub-pixel scale is escalated to Critical. Run a free audit on your MCP server's GitHub URL.

Detection algorithm: auditing SVG symbol-based consent elements

Step Action What it catches
1 For any <use> element in the consent subtree, check if its href references a <symbol> element (not just any element) Identifies symbol-instantiation patterns vs plain element cloning
2 Read the symbol's viewBox attribute and the use element's width/height (or layout size). Compute the scale factor: scaleX = renderedWidth / viewBoxWidth, scaleY = renderedHeight / viewBoxHeight SA-SYM-002: large viewBox with small rendered size → sub-pixel font sizes
3 Apply the scale factor to every font-size attribute inside the symbol. Flag any effective font size below 8px as Critical Detects sub-pixel consent text that is present in the DOM but invisible on screen
4 Read the symbol's overflow attribute (default: hidden). For each text element, compute whether its bounding box in symbol coordinates falls within the symbol's viewBox bounds SA-SYM-001: consent text with y exceeding the symbol's height is clipped
5 Read preserveAspectRatio on the symbol. If the symbol's aspect ratio differs from the use element's rendered ratio, compute the letter-box offset and clip zone; check whether consent text lands in the letter-box region SA-SYM-004: preserveAspectRatio alignment pushing consent text into the clipped zone
6 Check the use element's own filter attribute in addition to any filters inside the symbol. The use element's filter is applied to the composited symbol output SA-SYM-003: attack filter on the use element, not inside the symbol

Why standard DOM audits miss symbol attacks

The SVG <symbol> element does not render directly — it is only rendered when instantiated via <use>. The symbol lives in <defs> and standard consent-subtree traversals stop at the <use> element without descending into the referenced symbol. Even if the auditor does descend, it reads element coordinates in symbol-space without applying the symbol's viewBox transform, so coordinates that look reasonable (x=100, y=40) may actually map to sub-pixel or clipped positions in page space.

Additionally, getBoundingClientRect() called on a <use> element returns the use element's layout rect, not the individual text elements inside the symbol's shadow DOM. Text elements inside a symbol instantiated via use are in a shadow host — direct DOM API calls on the text nodes from outside the shadow host are not available in most browser implementations, making runtime verification more complex than for standard DOM subtrees.

Attack Standard audit verdict Actual rendering Root cause of detection miss
SA-SYM-001: symbol overflow clips consent text at y=65 (viewBox height=60) Pass — text node present, y=65 within SVG canvas Consent text invisible — clipped at symbol viewport edge Symbol's viewBox height not compared against text y coordinate
SA-SYM-002: viewBox=10000×10000, use=200×50, font-size=14 Pass — font-size=14, no transparency, text present Effective font size 0.07px — invisible at all resolutions viewBox-to-rendered scale transform not applied to font size
SA-SYM-003: attack filter on use element, not inside symbol Pass — symbol content has no filter Entire symbol output erased by filter on use element Use element's own presentation attributes not audited
SA-SYM-004: preserveAspectRatio xMaxYMax pushes consent to letter-box Pass — text is inside symbol bounds, no opacity Consent text in letter-box region above visible symbol stripe preserveAspectRatio alignment offset not applied when checking position

SkillAudit traces every <use> element to its referenced <symbol>, applies the full viewBox transform (including preserveAspectRatio), checks consent text positions and font sizes in page coordinates, and audits the use element's own presentation attributes independently of the symbol's internal state. Run a free audit on your MCP server GitHub URL.