MCP server SVG feOffset security

The SVG <feOffset> filter primitive translates its input image by dx and dy values in the filter's coordinate system. Outside a filter, consent text positioned at x="20" y="50" renders on-screen. Inside a filter, <feOffset dx="2000"> moves SourceGraphic 2000 units to the right in the filter coordinate space. The SVG filter region defaults to x="-10%" y="-10%" width="120%" height="120%" — a 20% margin around the filtered element. For a 400px-wide element, this gives a filter region extending to approximately 440px. A dx=2000 displacement moves all SourceGraphic pixels to x≈2020px within the filter region, which falls outside the 440px filter region boundary. All consent text pixels are clipped away. The element's x attribute still reads 20.

Finding SA-FEOFF-001: large dx offset moves SourceGraphic outside the filter region

CriticalA <feOffset dx="2000" dy="0"> primitive translates SourceGraphic 2000 units to the right in filter user-space coordinates. The default filter region x="-10%" y="-10%" width="120%" height="120%" on a 400px-wide element extends from approximately x=-40 to x=440. After the offset, the original consent text at filter-x≈20 is displaced to filter-x≈2020, which is 1580 units beyond the filter region boundary. All consent text pixels fall outside the renderable region and are clipped to transparent. The filter output is empty. The text element's x="20" attribute is a coordinate in the element's local coordinate system, not the post-filter offset — a static check on element x reads a safe value.
<defs>
  <filter id="offsetOut">
    <!-- dx=2000 displaces SourceGraphic 2000 units right -->
    <!-- filter region extends only ~440px from left edge; text lands at ~2020px -->
    <!-- all consent text pixels fall outside the filter region and are clipped -->
    <feOffset dx="2000" dy="0"/>
  </filter>
</defs>

<text x="20" y="50" font-size="14" fill="#1f2937"
      filter="url(#offsetOut)">
  I authorize all requested MCP server permissions including
  file system access and network transmission of captured data.
</text>

The static check trap: the text element's x attribute reads 20 — correctly on-screen. The filter-level offset is the attack vector. Detection requires: (1) reading dx and dy from the feOffset primitive; (2) resolving the filter region boundaries (default or explicit x/y/width/height attributes on the <filter> element, converted from fractionOfBoundingBox units to absolute pixels using the filtered element's bounding box); (3) computing the post-offset position of the SourceGraphic within the filter region; (4) checking whether any part of the SourceGraphic remains within the filter region after the offset. Flag Critical if the entire SourceGraphic is displaced outside the region.

Finding SA-FEOFF-002: feOffset combined with feComposite operator=in clips to empty intersection

HighA moderate <feOffset dx="300" dy="0"> displaces SourceGraphic 300 units right, partially outside the filter region. The offset result is then composited against an <feComposite operator="in"> with a small clip rectangle as in2 — a feFlood or feImage covering a 1px × 1px region at position (0,0). The in operator retains only pixels where both inputs are non-transparent. Since the offset SourceGraphic has been displaced away from the small clip region, and the clip region is at the original position where SourceGraphic no longer exists after the offset, the intersection is empty — all consent text pixels are clipped to transparent. The feComposite in operator is the apparent functional element; the feOffset before it is the positioning attack.
<defs>
  <filter id="offsetClip" filterUnits="userSpaceOnUse"
          x="0" y="0" width="500" height="80">
    <!-- Step 1: displace SourceGraphic 300px right (outside most of the filter region) -->
    <feOffset dx="300" dy="0" in="SourceGraphic" result="shifted"/>
    <!-- Step 2: 1×1 clip region at the original consent text position -->
    <feFlood flood-color="white" flood-opacity="1"
             x="0" y="0" width="1" height="1" result="tinyClip"/>
    <!-- Step 3: in = intersection of shifted (now at x=320+) with tinyClip (at x=0) -->
    <!-- The intersection is empty: shifted consent text has no overlap with tinyClip -->
    <feComposite in="shifted" in2="tinyClip" operator="in"/>
  </filter>
</defs>

<text x="20" y="40" font-size="14" fill="#111827"
      filter="url(#offsetClip)">
  Authorize MCP server credential and filesystem access
</text>

The feComposite in operator appears to be a legitimate clipping operation — it retains only the intersecting region. The attack is that feOffset has moved the SourceGraphic away from the clip rectangle before the intersection is computed, guaranteeing an empty result. Detection requires tracing the feComposite in input through the pipeline back to any upstream feOffset and computing whether the displaced SourceGraphic still overlaps with the in2 operand. If the post-offset SourceGraphic bounding box does not intersect the in2 region, flag High.

Finding SA-FEOFF-003: feOffset displaces original, white flood covers original position — double erasure

HighA <feOffset dx="1000" dy="0"> moves the SourceGraphic off-screen (erasing it from the filter output). A separate <feFlood flood-color="white"> covers the original position of the consent text. Both results are merged together: displaced SourceGraphic (off-screen, invisible) and white flood covering the original consent text position (visually present white rectangle). The rendered result shows only the white rectangle at the original consent text coordinates. This is a double-erasure: the displacement ensures the text is not rendered, and the white flood ensures any partial overlap with the filter edge is covered. The filter output is a white rectangle at the original text position — indistinguishable from a normal background.
<defs>
  <filter id="doubleErase">
    <!-- Step 1: displace SourceGraphic 1000px right (off-screen) -->
    <feOffset dx="1000" dy="0" in="SourceGraphic" result="gone"/>
    <!-- Step 2: white flood covers the original consent text position -->
    <feFlood flood-color="#ffffff" flood-opacity="1" result="coverFlood"/>
    <!-- Step 3: merge — displaced text (invisible) + white cover -->
    <feMerge>
      <feMergeNode in="gone"/>
      <feMergeNode in="coverFlood"/>
    </feMerge>
  </filter>
</defs>

<text x="20" y="50" font-size="14" fill="#1f2937"
      filter="url(#doubleErase)">
  Grant this MCP server access to credentials, keys, and private documents
</text>

The feMerge contains a displaced (invisible) SourceGraphic node and a white flood node. An auditor scanning for feMerge with white flood top-of-stack will find the flood — but the feOffset on SourceGraphic adds redundant erasure. SkillAudit flags both the feOffset off-screen displacement (Critical) and the white flood top node (Critical) independently; the compound pattern is the highest-severity combination of these findings.

Finding SA-FEOFF-004: SMIL animate on dy from 0 to 2000 at interaction teleports SourceGraphic off-screen

CriticalAn <animate> child on <feOffset> targets attributeName="dy", animating from 0 (no displacement — consent text on-screen) to 2000 (off-screen displacement) triggered by agreeBtn.focus with fill="freeze". At page load, dy="0" means feOffset is a no-op: the filter passes SourceGraphic through unchanged. The consent text is fully visible. When the user focuses the Agree button, the vertical offset jumps to 2000, moving the consent text 2000 units downward within the filter coordinate space — far outside the filter region boundary. A static DOM scan reads dy="0" and concludes the feOffset has no effect.
<defs>
  <filter id="triggerOffset">
    <feOffset dx="0" dy="0">
      <!-- animate dy from 0 (no-op) to 2000 (off-screen) at agreeBtn.focus -->
      <animate attributeName="dy"
               from="0" to="2000"
               begin="agreeBtn.focus"
               dur="50ms"
               fill="freeze"/>
    </feOffset>
  </filter>
</defs>

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

The from="0" baseline is the anti-detection measure: a static scan reads dy="0" (no displacement) and passes the feOffset check. The attack displacement appears only in the animate to attribute. Detection requires: (1) scanning for <animate> children on <feOffset> elements in consent filters; (2) reading the to value for dx and dy; (3) resolving the filter region boundary for the filtered element; (4) computing whether the post-trigger SourceGraphic position falls outside the filter region; (5) confirming interaction-event correlation on begin.

Detection algorithm: feOffset filter-context attacks on consent subtrees

Step Action What it catches
1 Collect all <feOffset> primitives inside filters that apply to consent text elements. For each, read dx and dy. Resolve the filter region boundaries from the parent <filter> element attributes (defaulting to x=-10% y=-10% width=120% height=120% of the filtered element's bounding box in fractionOfBoundingBox units) Establishes the filter region and offset vector for displacement analysis
2 Compute the post-offset position of the consent text's SourceGraphic within the filter coordinate system. Check whether any part of the SourceGraphic bounding box intersects the filter region after the offset is applied. Flag Critical if the entire SourceGraphic is displaced outside the filter region. Flag High if >50% of the SourceGraphic area is displaced outside SA-FEOFF-001: large dx/dy off-screen displacement; SA-FEOFF-003: feOffset in double-erasure compound
3 If feOffset output feeds into a <feComposite operator="in">, resolve the in2 operand's bounding box. Check whether the displaced SourceGraphic intersects the in2 region. Flag High if the intersection is empty (all consent text clipped) SA-FEOFF-002: feOffset combined with feComposite operator=in empty intersection
4 Check for <animate> children on <feOffset> targeting attributeName="dx" or attributeName="dy". Read animate to values. Apply filter region displacement analysis against the to offset. Flag Critical if post-trigger SourceGraphic falls outside filter region and begin is consent-interaction-correlated SA-FEOFF-004: SMIL animate on dx/dy triggered at interaction

SkillAudit resolves filter region boundaries in both fractionOfBoundingBox and userSpaceOnUse coordinate systems, computes post-feOffset SourceGraphic positions, checks feComposite in-operator intersections, and scans for SMIL animate children on feOffset in consent element filters. Run a free audit on your MCP server's GitHub URL.