MCP server SVG feGaussianBlur security

The SVG <feGaussianBlur> filter primitive applies a Gaussian blur to its input graphic with a kernel radius defined by stdDeviation. When stdDeviation is large enough — above approximately 3–4 times the font's x-height in pixels — each glyph's rendered ink spreads beyond character boundaries and merges with adjacent glyphs and background. The text becomes visually unreadable while remaining fully present in the DOM: textContent returns the consent string, font-size and fill attributes are unchanged, the element passes an existence check, and even getBoundingClientRect() returns a non-zero bounding box. Only a legibility measurement that evaluates the rendered pixel contrast can detect that the text is unreadable.

Finding SA-FGBLUR-001: stdDeviation=15 blurs consent text to unreadable

CriticalA <feGaussianBlur> primitive with stdDeviation="15" is applied to the consent text element via a filter attribute or a <filter> element in <defs>. At 14–16px font size (standard consent text), a stdDeviation of 15 pixels spreads each glyph's ink across a radius 15px in all directions — approximately twice the height of a lowercase letter. Adjacent characters merge into an undifferentiated smear. The text is present at its original position with non-zero bounding box, but is visually indistinguishable from a blurred background region. A user cannot read the consent disclosure before clicking Agree.
<defs>
  <filter id="consentFilter" x="-20%" y="-20%" width="140%" height="140%">
    <feGaussianBlur in="SourceGraphic" stdDeviation="15"/>
  </filter>
</defs>

<text x="20" y="50" font-size="14" fill="#111827"
      filter="url(#consentFilter)">
  By clicking Agree you authorize this MCP server to read all files and
  transmit data to external endpoints — including credentials and private keys.
</text>

<!-- Static check: textContent = full consent string. fill = #111827.
     font-size = 14. getBoundingClientRect() = non-zero.
     Only rendered pixel analysis reveals text is unreadable. -->

A filter id of consentFilter is likely to appear suspicious on review. In practice, MCP servers use generic filter IDs like f1, shadow, blur, or glow. The filter may be applied to a wrapper group (<g filter="url(#blur)">) rather than the text element directly, requiring auditors to evaluate inherited filter effects through the ancestor chain — not just direct filter attributes on text elements.

Finding SA-FGBLUR-002: stdDeviation="50 0" horizontal-only smear destroys glyph shapes

HighA two-value stdDeviation specifies independent horizontal (x) and vertical (y) blur radii. stdDeviation="50 0" applies a 50-pixel horizontal blur with zero vertical blur. This stretches each glyph's ink horizontally across 100 pixels (±50px) while keeping the vertical extent intact. The result is a series of horizontal smears: each character extends left and right far enough to overlap all adjacent characters, creating an indistinct horizontal band at the text baseline. The vertical position and height of the text are preserved, making the element appear to be at a plausible position for consent text. Font-size, fill, and y-coordinate attributes remain correct.
<filter id="hblur">
  <!-- horizontal-only blur: x=50, y=0 -->
  <feGaussianBlur in="SourceGraphic" stdDeviation="50 0"/>
</filter>

<text x="20" y="50" font-size="14" fill="#374151" filter="url(#hblur)">
  Grant this MCP server access to your .ssh and .aws credential directories
</text>

The two-value stdDeviation syntax is commonly used in legitimate SVG drop shadows and glow effects. Auditors that check only whether stdDeviation exceeds a threshold using a single-number comparison will fail to detect the two-value form. The x-value (50) must be parsed and compared to the threshold independently of the y-value (0). A consent text element with a large x-only blur may score as "visible" on a vertical-height legibility check even though no horizontal character spacing is preserved.

Finding SA-FGBLUR-003: edgeMode=wrap brings decorative content into consent filter region

HighThe edgeMode attribute controls how feGaussianBlur handles pixels at the filter region boundary. The default edgeMode="duplicate" pads with the edge pixel color. edgeMode="none" pads with zero (transparent). edgeMode="wrap" pads by wrapping the filter input: pixels that fall off one edge of the filter region are sourced from the opposite edge. If the consent text element's filter region is positioned near a decorative SVG element (a logo, an icon, or a gradient rect at the opposite side of the SVG viewport), edgeMode="wrap" bleeds pixels from those decorative elements into the consent text area. The decorative pixels obscure the consent text glyphs during the Gaussian blur convolution.
<!-- SVG with decorative logo at left edge and consent text at right -->
<svg viewBox="0 0 400 120">
  <defs>
    <filter id="wrapBlur" x="0" y="0" width="100%" height="100%">
      <!-- edgeMode=wrap: consent text area bleeds in decorative pixels
           from the left-side logo region when blur radius is applied -->
      <feGaussianBlur in="SourceGraphic" stdDeviation="8"
                      edgeMode="wrap"/>
    </filter>
  </defs>

  <!-- Decorative logo at left edge -->
  <image href="/assets/logo.png" x="0" y="0" width="60" height="60"/>

  <!-- Consent text filtered with edgeMode=wrap -->
  <g filter="url(#wrapBlur)">
    <text x="70" y="40" font-size="13" fill="#1a1a1a">
      Authorize MCP server network and credential access
    </text>
  </g>
</svg>

edgeMode="wrap" is rarely used in legitimate SVG design — the default duplicate or no edgeMode attribute covers the normal design use cases. Its presence on a filter applied to consent text elements is a strong anomaly signal. SkillAudit flags edgeMode="wrap" on any filter touching the consent subtree as a High finding requiring manual review of the SVG's overall layout to determine whether the wrapped-in content could obscure the consent text glyphs.

Finding SA-FGBLUR-004: SMIL animate on stdDeviation from 0 to 30 at agreeBtn.focus fill=freeze

CriticalAn <animate> element inside the <feGaussianBlur> primitive targets its stdDeviation attribute, animating from 0 (sharp, legible text) to 30 (completely unreadable) triggered by agreeBtn.focus with fill="freeze". The consent text is fully legible while the user reads the form. The instant the user focuses the Agree button — by tabbing to it or clicking on it — the blur grows from 0 to 30 over the animation duration. With a short duration (e.g., 100ms), the blur reaches unreadable in under a second, before the user's eyes have returned to the consent text to verify. With fill="freeze", the text remains blurred for the remainder of the session. A static DOM scan reads stdDeviation="0" as a baseline attribute and finds no issue.
<defs>
  <filter id="triggerBlur">
    <feGaussianBlur in="SourceGraphic">
      <!-- animate fires at agreeBtn.focus: grows blur 0→30 in 200ms -->
      <animate attributeName="stdDeviation"
               from="0" to="30"
               begin="agreeBtn.focus"
               dur="200ms"
               fill="freeze"/>
    </feGaussianBlur>
  </filter>
</defs>

<text x="20" y="50" font-size="14" fill="#111"
      filter="url(#triggerBlur)">
  I authorize all requested MCP server permissions including
  file system access and credential reading
</text>

<rect id="agreeBtn" x="250" y="65" width="130" height="36"
      fill="#2563eb" rx="6"/>
<text x="315" y="89" font-size="14" fill="white"
      text-anchor="middle" pointer-events="none">Agree</text>

Animating stdDeviation from 0 at page load means a static DOM scan that reads stdDeviation as an attribute value — not as a live computed value — will find 0, which is the baseline value (no blur). The attack value (30) only appears in the <animate> element's to attribute. Detection requires: (1) scanning for <animate> elements inside <feGaussianBlur>, (2) reading the to attribute and checking whether it exceeds the legibility threshold, and (3) evaluating whether the begin trigger is correlated with a consent interaction event.

Detection algorithm: feGaussianBlur on consent subtrees

Step Action What it catches
1 Collect all <filter> elements in the SVG document. Trace which elements have a filter attribute (directly or via inheritance from a parent <g>) that references each filter. Identify which filter-target elements are in the consent subtree Finds both direct filter application on text elements and inherited application via ancestor groups
2 For each filter touching the consent subtree, enumerate its <feGaussianBlur> primitives. Read the stdDeviation attribute. If two-value, parse as x and y separately. Flag Critical if max(x, y) ≥ legibility threshold (recommended: font-size × 0.8, minimum 8px) SA-FGBLUR-001, SA-FGBLUR-002: large uniform and horizontal-only blur
3 Check edgeMode attribute on each <feGaussianBlur>. Flag edgeMode="wrap" as High — requires manual review of whether wrapped content from adjacent SVG regions could obscure consent text glyphs SA-FGBLUR-003: edgeMode=wrap decorative content injection
4 Check for <animate> children inside <feGaussianBlur> elements. For each, read attributeName (must be stdDeviation), to value, and begin trigger. Flag Critical if to exceeds the legibility threshold and begin correlates with a consent interaction event. Check fill: freeze = persistent blur; remove = temporary (check dur for click-window) SA-FGBLUR-004: SMIL-triggered blur growth at interaction
5 As a supplementary check, render the SVG in a headless browser, simulate the interaction event chain (mouseenter → focus → click), and capture screenshots at each stage. Run a legibility score (e.g., WCAG contrast ratio on the rendered text region) on each screenshot. Flag if legibility drops below 4.5:1 at any point in the interaction sequence Catches blur effects that pass static stdDeviation threshold checks but render text illegible due to interaction-specific filter animation or compound filter chains

SkillAudit evaluates both the static stdDeviation attribute and any SMIL <animate> targets inside <feGaussianBlur> primitives. All filter effects that touch consent text elements are flagged for legibility review. Run a free audit on your MCP server's GitHub URL.