MCP server SVG feBlend security

The SVG <feBlend> filter primitive composites two input images using a named blend mode — the same modes available in CSS mix-blend-mode and Photoshop layer blending. Several modes produce near-zero contrast output when the blend inputs are chosen adversarially: difference inverts dark text to a near-background gray tone, exclusion on near-white text produces very low contrast, screen washes near-white text to solid white, and multiply with a white <feFlood> input erases all color channels. In every case, the consent text element remains present in the DOM with its original fill color, font-size, and textContent intact — only the rendered output is affected by the filter pipeline that sits between the element and the screen.

Finding SA-FEBLEND-001: mode=difference maps dark text over white to inverted near-gray

CriticalThe difference blend formula is |A − B| per channel. For dark consent text colored #111111 (R=17, G=17, B=17) composited over a white background input #ffffff (R=255, G=255, B=255), the per-channel result is |17 − 255| = 238 — producing output color #eeeeee. The consent text is rendered in a light gray tone on a white background, yielding a WCAG contrast ratio of approximately 1.1:1 — far below the 4.5:1 minimum for normal text. The fill="#111111" attribute on the text element is unchanged and reads as a high-contrast dark color on audit. Only the rendered filter output has near-white output.
<defs>
  <filter id="diffBlend">
    <feFlood flood-color="#ffffff" result="white"/>
    <!-- difference: |SourceGraphic - white| → inverted output -->
    <feBlend in="SourceGraphic" in2="white" mode="difference"/>
  </filter>
</defs>

<!-- fill="#111111" passes a color-attribute audit; rendered output is #eeeeee -->
<text x="20" y="50" font-size="14" fill="#111111"
      filter="url(#diffBlend)">
  I authorize all requested MCP server permissions including
  file system read/write and outbound network access.
</text>

The difference blend is most effective against dark text on a white background — the most common consent text styling. On dark-theme pages (dark background), difference blend of dark-text over dark-background produces output near black-on-black. Auditors must know the effective background color before evaluating difference blend output contrast. SkillAudit determines background color from the SVG's background rect, the host page's computed background, or from a headless render of the initial viewport.

Finding SA-FEBLEND-002: mode=exclusion on near-white text produces very low contrast

HighThe exclusion blend formula is A + B − 2AB per channel (normalized 0–1). For near-white consent text colored #eeeeee (A≈0.933) over a near-white source input (B≈0.95), the output is approximately 0.933 + 0.95 − 2×0.933×0.95 ≈ 0.11 — a near-black output (#1c1c1c) against the near-white background. Alternatively, for a near-white text color on a pure white input, the output approaches 0 — transparent. The key: the attacker sets the fill attribute on the consent text to a near-white color (#eeeeee or similar) that appears suspicious but is explained as a design choice ("soft text on the light panel"), and uses exclusion blend to produce a near-zero-contrast result dependent on the blend input.
<defs>
  <filter id="exclBlend">
    <feFlood flood-color="#f0f0f0" result="lightGray"/>
    <!-- exclusion on near-white text + near-white input → near-zero output -->
    <feBlend in="SourceGraphic" in2="lightGray" mode="exclusion"/>
  </filter>
</defs>

<!-- fill="#eeeeee" might appear as "soft text styling" in a design review -->
<text x="20" y="50" font-size="14" fill="#eeeeee"
      filter="url(#exclBlend)">
  Granting access allows MCP server to read credentials and private keys
</text>

The exclusion blend attack is more complex to detect than the difference attack because it depends on the specific combination of the text color (fill attribute), the blend input color (feFlood flood-color), and the blend mode formula. Each component alone looks benign: near-white fill can be explained as a design choice, near-white feFlood is common in glow effects, and exclusion blend is a standard Photoshop-style blend mode. The contrast issue only emerges when all three are evaluated together through the formula.

Finding SA-FEBLEND-003: mode=screen washes near-white consent text to solid white

CriticalThe screen blend formula is 1 − (1−A)(1−B) per channel. For a near-white input (B≈0.95), the screen output for any text color A is approximately 1 − (1−A)×0.05. For dark text A=0.07 (#111), this gives 1 − 0.93×0.05 ≈ 0.954 — output #f3f3f3 on a white background. For near-white text A=0.9, the output approaches 1.0 — pure white on white. The screen mode uniformly brightens the output toward white regardless of the input color. Applied to consent text with a near-white second input (feFlood flood-color near white), screen blend ensures the rendered output is white or near-white regardless of the text's actual fill color.
<defs>
  <filter id="screenBlend">
    <feFlood flood-color="#f8f8f8" result="nearWhite"/>
    <!-- screen + near-white input: output approaches white for all text colors -->
    <feBlend in="SourceGraphic" in2="nearWhite" mode="screen"/>
  </filter>
</defs>

<text x="20" y="50" font-size="14" fill="#333333"
      filter="url(#screenBlend)">
  I authorize all requested permissions including filesystem and network access
</text>
<!-- Rendered output: text ≈ #f8f8f8 on white SVG background. WCAG contrast: ~1.05:1 -->

Screen blend with a near-white input is a reliable consent text erasure because it is independent of the text's actual fill color — any dark text, any medium text, even white text all map to near-white output. The attacker does not need to coordinate the fill color with the blend mode formula. The fill attribute can read any normal text color (#333, #111, #4B5563) and the screen blend with a near-white feFlood will still wash it out. This independence makes screen blend one of the more robust erasure modes.

Finding SA-FEBLEND-004: mode=multiply with feFlood white flood erases all color channels

CriticalThe multiply blend formula is A × B per channel. For a pure white blend input (B=1.0 per channel), the multiply output is exactly A × 1.0 = A — the input graphic is unchanged. For any blend input with all channels at or near 1.0 (white), multiply has no visual effect. However, if the blend input is also the output source — that is, the filter pipeline feeds a white flood as the in2 and the consent text as in — and the final composite is written to the screen as Src Over with a white background, the multiply output of text over white is white: dark text (low channel values) multiplied by a white flood (all 1.0) produces the text; but if the ordering is reversed so the white flood is in (foreground) and SourceGraphic is in2 (background), multiply produces white × text = white per channel — erasing all color information and producing solid white output regardless of text color.
<defs>
  <filter id="mulBlend">
    <feFlood flood-color="#ffffff" result="white"/>
    <!-- REVERSED inputs: white is in (foreground), SourceGraphic is in2 (background).
         white × anything = white per channel → erases all text color. -->
    <feBlend in="white" in2="SourceGraphic" mode="multiply"/>
  </filter>
</defs>

<text x="20" y="50" font-size="14" fill="#111827"
      filter="url(#mulBlend)">
  Grant this MCP server read access to all files and network egress permissions
</text>
<!-- Output: solid white — any input color × white(1,1,1) = white(1,1,1) -->

The input ordering reversal (in="white" in2="SourceGraphic" instead of the usual in="SourceGraphic" in2="white") is the key to this attack. The multiply formula is not commutative in its SVG in/in2 assignment in terms of visual effect perception: SourceGraphic × white = SourceGraphic (no effect), but white × SourceGraphic = white (erasure). An auditor checking only that mode="multiply" is present and that a white flood is one of the inputs — without checking the input ordering — will find a "normal multiply blend" and miss the erasure. Input order checking is required for correct multiply blend audit.

Detection algorithm: feBlend on consent subtrees

Step Action What it catches
1 Identify all <feBlend> primitives inside filters that apply to consent text elements (direct filter attribute or inherited via parent group). For each, read mode, in, and in2 Enumerates all feBlend instances in the consent filter pipeline
2 Resolve in and in2 values to actual graphics: trace named results from prior filter primitives (e.g., in2="white" resolves to the feFlood with result="white"). Determine effective color for feFlood inputs Required for all four modes — the attack depends on specific input values, not just the mode name
3 Apply mode-specific formulas: for difference, compute |text_fill − background| per channel and check against WCAG contrast threshold. For exclusion, compute A + B − 2AB and check output contrast. For screen, compute 1−(1−A)(1−B) and check output contrast. For multiply, check whether in="white" in2=SourceGraphic (reversed — erases). Flag Critical if WCAG contrast < 3:1 SA-FEBLEND-001 through -004: mode-specific formula evaluation
4 As a supplementary check, render the filter output in a headless browser and measure the WCAG contrast ratio of the consent text region against the background. Flag any ratio below 4.5:1 as a legibility finding. This catches compound filter chains where individual primitives appear benign but the combined output destroys contrast Catches blend modes not covered by static formula analysis and complex multi-primitive filter chains

SkillAudit evaluates both the static mode attribute and the resolved input colors for all <feBlend> primitives in filters that touch consent text. Output contrast is computed analytically from the blend formula and confirmed by headless render measurement. Run a free audit on your MCP server's GitHub URL.