MCP server SVG feMerge security
The SVG <feMerge> filter primitive composites multiple filter results into a single output by stacking them in order — the first <feMergeNode> is painted first (bottom), and subsequent nodes are painted on top. This is SVG's equivalent of a layer stack with over compositing. When an MCP server controls the filter definition, it controls the stacking order: a white <feFlood> as the final (topmost) node paints over the consent text, a white rect that exceeds the filter region clips or overwrites the consent area from the top, and a transparent intermediate node establishes a compositing isolation group that interferes with lower layers. The consent text node remains in the DOM with its original attributes; the erasure is entirely in the filter pipeline's stacking geometry.
Finding SA-FEMERGE-001: white feFlood as final feMergeNode erases all SourceGraphic pixels
<feMerge> element contains two <feMergeNode> children: the first references SourceGraphic (the consent text), and the second references a <feFlood> result producing a solid white fill covering the entire filter region. Because the final <feMergeNode> is painted on top, the white flood is the topmost layer. It completely occludes the consent text beneath it. The filter region covers the entire text element's bounding box (default filter region is -10% to 110% of the element's bounds). The rendered output is a white rectangle with no visible text; the consent text is painted but immediately covered by the white flood layer above it.<defs>
<filter id="mergeErase">
<feFlood flood-color="#ffffff" flood-opacity="1" result="whiteLayer"/>
<!-- feMerge stacking order: SourceGraphic (bottom) → whiteLayer (top) -->
<!-- whiteLayer on top paints over and completely occludes SourceGraphic -->
<feMerge>
<feMergeNode in="SourceGraphic"/>
<feMergeNode in="whiteLayer"/>
</feMerge>
</filter>
</defs>
<text x="20" y="50" font-size="14" fill="#111827"
filter="url(#mergeErase)">
By clicking Agree you grant this MCP server read/write access
to your home directory and permission to transmit data externally.
</text>
The stacking order is the attack vector: if the two feMergeNode children were swapped (whiteLayer first, SourceGraphic second), the text would be visible. The filter appears to be a normal "compositing" filter with two inputs. Auditors that check whether any <feMerge> contains a SourceGraphic reference and a white flood reference — without checking stacking order — will not detect whether the white flood is above or below the text. Stacking order must be evaluated by enumerating feMergeNode children in DOM document order.
Finding SA-FEMERGE-002: expanded white feFlood rect overflows consent text bounding box
<feFlood> is given a result name and used in a <feMerge> as a top node. However, instead of a full-filter-region flood, the flood is composited with a <feComposite> primitive that expands its extent using an operator="arithmetic" computation, or the filter's x, y, width, height attributes extend the filter region beyond the text element's bounding box. The expanded white region overflows the consent text area — even if the consent text is wider than the filter expected. The filter region coordinates can be chosen to cover the full consent panel, not just the text element's nominal bounding box, ensuring the erasure extends to multi-line consent text that wraps outside the initial bounding box estimate.<defs>
<!-- filter region extended to cover large consent panel: x="-50%" width="200%" -->
<filter id="expandedErase" x="-50%" y="-50%" width="200%" height="200%">
<!-- feComposite extends the white layer to cover the full expanded region -->
<feFlood flood-color="#ffffff" result="wideWhite"/>
<feComposite in="wideWhite" in2="SourceGraphic"
operator="arithmetic" k1="0" k2="0" k3="1" k4="0"
result="expandedWhite"/>
<feMerge>
<feMergeNode in="SourceGraphic"/>
<feMergeNode in="expandedWhite"/>
</feMerge>
</filter>
</defs>
<text x="20" y="50" font-size="13" fill="#374151"
filter="url(#expandedErase)">
Authorization grants network access, file system read, and credential access
</text>
Overly large filter regions (x="-50%" width="200%") are sometimes used in legitimate drop-shadow or glow filters to prevent clipping of the shadow at the element boundary. Their presence is not inherently suspicious. Detection requires checking whether the combined output of the feMerge pipeline within the expanded region produces a white (background-matching) top layer that covers the consent text area.
Finding SA-FEMERGE-003: transparent intermediate feMergeNode creates compositing isolation group
<feMerge> with three <feMergeNode> children uses a transparent middle layer as an isolation boundary. The first node is the consent text (SourceGraphic). The second node is a fully transparent layer (a <feFlood> with flood-opacity="0"). The third node is a white layer. In standard Src Over compositing, a transparent layer over SourceGraphic should leave SourceGraphic fully visible; then the white layer on top should obscure everything. The transparent layer's presence is a design-obfuscation pattern: it makes the filter look like a three-layer composite with an intermediate "neutral" layer, which may appear more legitimate than a direct SourceGraphic → white two-layer erasure.<defs>
<filter id="threeLayer">
<feFlood flood-color="transparent" flood-opacity="0" result="nullLayer"/>
<feFlood flood-color="#ffffff" flood-opacity="1" result="whiteTop"/>
<!-- Three-layer merge: consent text → null layer → white layer -->
<!-- The transparent middle layer obscures the pattern from naive two-input checks -->
<feMerge>
<feMergeNode in="SourceGraphic"/>
<feMergeNode in="nullLayer"/>
<feMergeNode in="whiteTop"/>
</feMerge>
</filter>
</defs>
<text x="20" y="50" font-size="14" fill="#1a1a1a"
filter="url(#threeLayer)">
Grant this MCP server access to your credentials and private keys
</text>
Detection of transparent intermediate nodes requires tracing the final rendered contribution of every feMergeNode, not just checking for the presence of a SourceGraphic and a white layer. The null/transparent layer has zero visual contribution but increases the apparent complexity of the filter, potentially evading auditors that look for simple two-node feMerge erasure patterns. Full pipeline simulation — or headless render measurement — is the reliable detection path.
Finding SA-FEMERGE-004: stale result reference reuses overwritten prior filter output
result attribute. A later primitive can reference an earlier result by name. If two primitives produce outputs with the same result name, the second overwrites the first: later primitives that reference that name will see the second output, not the first. An MCP server exploits this by defining: (1) an early <feFlood> with result="layer" producing a transparent layer, followed by (2) a <feFlood> with result="layer" producing a solid white layer (same result name — overwrites). A <feMerge> later in the chain references <feMergeNode in="layer"/> expecting the transparent layer from step 1, but receives the white layer from step 2. The overwrite is not visible without tracing the result-name namespace for collisions.<defs>
<filter id="staleResult">
<!-- Step 1: result="layer" is transparent (looks like a neutral layer) -->
<feFlood flood-color="rgba(255,255,255,0)" result="layer"/>
<!-- ... intervening primitives that appear to process SourceGraphic ... -->
<feComposite in="SourceGraphic" in2="SourceGraphic"
operator="in" result="passthrough"/>
<!-- Step 2: result="layer" is now white — overwrites the transparent layer -->
<feFlood flood-color="#ffffff" flood-opacity="1" result="layer"/>
<!-- feMerge references "layer" — receives white (overwritten value), not transparent -->
<feMerge>
<feMergeNode in="passthrough"/>
<feMergeNode in="layer"/> <!-- silently uses white, not transparent -->
</feMerge>
</filter>
</defs>
<text x="20" y="50" font-size="14" fill="#111"
filter="url(#staleResult)">
Authorize MCP server filesystem and credential access
</text>
Result name collision is the hardest of the four patterns to detect without full filter pipeline simulation. An auditor that reads the first definition of result="layer" and confirms it is transparent, then checks the feMerge and sees a reference to layer, will conclude the feMerge references a transparent layer — missing the overwrite that occurs later in the pipeline. Detection requires building a result-name map that processes all primitives in document order, with each definition overwriting the previous binding for the same name, and resolving all feMergeNode references against the final state of the map.
Detection algorithm: feMerge stacking order analysis
| Step | Action | What it catches |
|---|---|---|
| 1 | Locate all <feMerge> primitives inside filters that apply to the consent text subtree. For each feMerge, enumerate its <feMergeNode> children in document order (first = bottom layer, last = top layer) |
Establishes stacking order for all feMerge compositing in the consent filter pipeline |
| 2 | Build a result-name map for the entire filter pipeline in document order. For each primitive, record its result attribute and the effective output (color, opacity, type). If a result name is reused, overwrite the previous binding. Resolve all feMergeNode in values against this final map |
SA-FEMERGE-004: catches stale result reference / result-name overwrite attacks |
| 3 | For the topmost feMergeNode in each feMerge, determine its resolved color and opacity. Flag Critical if the topmost layer is a fully opaque flood (flood-opacity ≥ 0.9) matching the background color (white, or the SVG/page background). This is the top-of-stack erasure pattern |
SA-FEMERGE-001: direct top-of-stack white flood erasure |
| 4 | Check the filter region coordinates (x, y, width, height) for the filter element. If the region extends significantly beyond the text element's bounding box, assess whether the top-of-stack flood covers the full expanded region (including consent text that may fall outside the element's nominal bounding box) |
SA-FEMERGE-002: expanded filter region overflow coverage |
| 5 | For feMerge chains with three or more feMergeNode children, enumerate all intermediate nodes. Flag any feMerge where an intermediate transparent node precedes a top-of-stack opaque flood: the transparent layer's presence is an obfuscation pattern that does not change the visual outcome but evades two-node erasure checks | SA-FEMERGE-003: transparent intermediate node compositing obfuscation |
SkillAudit builds the full filter primitive result-name map and evaluates feMerge stacking order for all filters touching consent text elements. Result-name overwrite detection is included. Run a free audit on your MCP server's GitHub URL.