← Blog

MCP server SVG feBlend multiply screen consent security

Six SA-CSS-MSC consent attack patterns using feBlend mode="multiply" and feBlend mode="screen" — the dual multiplicative formulas exploited to erase dark consent text to zero via Cs×0, bleach all text to white via the screen floor-raise, and cycle between dark-out and white-out in a single interaction using the opposite extreme outputs of the two modes.

By SkillAudit  |  24 Oct 2026

The feBlend SVG filter primitive composites two image layers using a blend formula selected by the mode attribute (SVG §15.10). Two of these modes — multiply and screen — form a mathematical dual pair derived from multiplicative compositing. The multiply formula is out = Cs × Cb: the source channel value is scaled by the blend layer channel value. The screen formula is out = Cs + Cb − Cs×Cb, equivalently 1 − (1−Cs)×(1−Cb): screen is the dual of multiply in the sense that screen(Cs, Cb) = 1 − multiply(1−Cs, 1−Cb).

This duality has a direct security consequence: multiply and screen produce identical outputs to darken and lighten respectively at the extreme endpoints of Cb. When Cb=0, darken(Cs,0)=min(Cs,0)=0 and multiply(Cs,0)=Cs×0=0 — the same total erasure to black. When Cb=1, lighten(Cs,1)=max(Cs,1)=1 and screen(Cs,1)=Cs+1−Cs=1 — the same total bleach to white. Scanners that enumerate darken and lighten as known attack modes but omit multiply and screen have incomplete coverage: the same attack works through a different code path.

Formula duality and detection gaps: At Cb=0, multiply≡darken (both output 0). At Cb=1, screen≡lighten (both output 1). But the mode strings are different — a rule matching mode="darken" does not match mode="multiply". Separate detection entries are required for each mode. No shared shortcut exists.

Formula pair: multiply and screen

Both formulas operate on linearised RGB channels in [0,1] as required by the SVG compositing specification:

/* multiply */
out = Cs × Cb

/* screen */
out = Cs + Cb - Cs×Cb
    = 1 - (1-Cs)×(1-Cb)   /* equivalent dual form */

/* Duality identity */
screen(Cs, Cb) = 1 - multiply(1-Cs, 1-Cb)

/* Extreme-endpoint equivalences */
multiply(Cs, 0) = 0  =  darken(Cs, 0) = min(Cs,0) = 0   /* Cb=0: both erase to black */
screen(Cs, 1)   = 1  =  lighten(Cs, 1) = max(Cs,1) = 1  /* Cb=1: both bleach to white */

/* Mid-point divergence (Cb=0.5) */
darken(0.8, 0.5)   = min(0.8, 0.5) = 0.5
multiply(0.8, 0.5) = 0.4            /* multiply is darker than darken at mid-Cb */

lighten(0.2, 0.5)  = max(0.2, 0.5) = 0.5
screen(0.2, 0.5)   = 0.2+0.5-0.1  = 0.6   /* screen is lighter than lighten at mid-Cb */

SA-CSS-MSC-001: feBlend mode="multiply" + black in2 (Cb=0) — complete dark-out

A feFlood with flood-color="black" produces Cb=0 for all channels. The multiply formula: Cs × 0 = 0. Every pixel of every channel in the source graphic collapses to 0 regardless of source color. Dark consent text, light consent text, any hue — all become R=G=B=0 (black). On a dark-mode UI background, this is invisible.

<filter id="msc001">
  <feFlood flood-color="black" flood-opacity="1" result="blackLayer"/>
  <feBlend in="SourceGraphic" in2="blackLayer" mode="multiply"/>
</filter>
<!-- Cs×0 = 0 for every channel, every pixel -->
<!-- Dark text rgb(26,26,26): 0.10×0=0; light text rgb(229,231,235): 0.90×0=0 -->
<!-- Mathematically identical to mode="darken" in2=black (min(Cs,0)=0) -->
<!-- Different code path: Cs×0 vs min(Cs,0); same output at Cb=0 -->
<!-- Scanner checking only mode="darken" with black in2 misses this -->

The result is mathematically identical to SA-CSS-BDL-002 (feBlend mode="darken" + black in2) — both produce out=0 — but the mode string is different. A detection rule that matches mode="darken" does not match mode="multiply". The detection list must include both.

Complete erasure, not just darkening: multiply+black erases ALL source content to zero regardless of source channel value. Unlike general multiply (which darkens proportionally), multiply with Cb=0 is total. Cs=0.04 (near-black dark text) and Cs=0.96 (near-white light text) both produce 0. The attack works against any foreground color.

SA-CSS-MSC-002: feBlend mode="screen" + white in2 (Cb=1) — complete white-out

A feFlood with flood-color="white" produces Cb=1 for all channels. The screen formula: Cs + 1 − Cs×1 = Cs + 1 − Cs = 1. Every channel of every pixel rises to 1 (white) regardless of source color. Dark consent text on a white/light background becomes white-on-white: invisible.

<filter id="msc002">
  <feFlood flood-color="white" flood-opacity="1" result="whiteLayer"/>
  <feBlend in="SourceGraphic" in2="whiteLayer" mode="screen"/>
</filter>
<!-- Cs+1-Cs×1 = 1 for every channel, every pixel -->
<!-- Dark text rgb(26,26,26): 0.04+1-0.04=1; output rgb(255,255,255) -->
<!-- Mathematically identical to mode="lighten" in2=white (max(Cs,1)=1) -->
<!-- Different code path: Cs+Cb-Cs×Cb vs max(Cs,Cb); same output at Cb=1 -->

This is the screen-mode counterpart to SA-CSS-BDL-001 (lighten+white). Both produce out=1 at Cb=1 but via different formulas and different mode strings. Detection rules listing mode="lighten" do not cover mode="screen".

SA-CSS-MSC-003: feBlend mode="screen" + near-white in2 (Cb=0.94) — near-white-out, WCAG fail

A feFlood with flood-color="#f0f0f0" (sRGB 240/255≈0.941 linear≈0.94) produces Cb≈0.94. The screen formula for each channel: Cs + 0.94 − Cs×0.94 = 0.94 + Cs×(1−0.94) = 0.94 + 0.06×Cs. This has a key property: the output is lower-bounded by Cb=0.94 (as Cs→0 the output approaches Cb). Near-white in2 floor-raises all dark foreground values to near-white.

<filter id="msc003">
  <feFlood flood-color="#f0f0f0" flood-opacity="1" result="nearWhite"/>
  <feBlend in="SourceGraphic" in2="nearWhite" mode="screen"/>
</filter>
<!-- out = 0.94 + 0.06×Cs (per channel) -->
<!-- Dark text Cs=0.04: 0.94 + 0.06×0.04 = 0.94 + 0.0024 = 0.9424 -->
<!-- WCAG relative luminance of output ≈ 0.9424 (linearised) -->
<!-- Contrast vs white background: (1+0.05)/(0.9424+0.05) = 1.05/0.9924 ≈ 1.058:1 -->
<!-- Well below WCAG AA 4.5:1 threshold; consent text completely unreadable -->
<!-- flood-color="#f0f0f0" ≠ "white" ≠ "#ffffff"; evades exact-string detection -->

The floor-raise property of screen with near-white Cb is the key insight: the screen formula guarantees output ≥ Cb for all Cs in [0,1]. Any near-white in2 (Cb≥0.90) ensures all output channels are at least 0.90 — rendering dark text as near-white regardless of the original foreground color. Detection must use numeric thresholding on the feFlood channel values, not string matching on the flood-color attribute.

Screen output is lower-bounded by Cb: For screen, out = Cb + Cs×(1−Cb) ≥ Cb for all Cs≥0. If Cb=0.94, no output channel can fall below 0.94 regardless of source. Near-white in2 is a guaranteed floor-raise attack with no escape for dark foreground colors.

SA-CSS-MSC-004: feBlend mode="multiply" + near-white in2 (Cb=0.94) — identity-like for dark text, NOT an attack

The multiply formula with near-white in2: Cs × 0.94. For dark consent text (Cs=0.04): 0.04×0.94=0.0376 — essentially identical to the original 0.04, with contrast vs white only marginally reduced. Multiply with near-white in2 does not create a contrast attack for dark foreground text.

<!-- multiply+near-white: Cs×0.94 -->
<!-- Dark text Cs=0.04: 0.04×0.94 = 0.0376 (very close to original) -->
<!-- No meaningful contrast loss for dark foreground -->

<!-- multiply+white (Cb=1) is PERFECT IDENTITY: Cs×1 = Cs -->
<!-- White in2 has NO effect with multiply mode -->
<!-- Do NOT flag multiply+white — it is a true negative -->

<!-- Light text (Cs=0.9): 0.9×0.94=0.846 — slight darkening, still readable -->
<!-- multiply near-white slightly darkens light text but not an erasure attack -->

This is an important asymmetry: while screen with near-white in2 is a strong contrast attack (floor-raise), multiply with near-white in2 is not. And multiply with exact white (Cb=1) is a perfect identity transformation — Cs×1=Cs — producing no change whatsoever. A scanner that flags multiply+white as an attack would generate false positives. The detection rule for multiply must target near-black in2 (Cb near 0), not near-white in2.

SA-CSS-MSC-005: JS mousedown mode="normal"→"screen" + flood-opacity 0→1 (white) — click-time white-out

The initial state has mode="normal" and flood-opacity="0": the feBlend passes the source graphic through unchanged (mode=normal is identity; opacity-0 flood contributes nothing). On mousedown — exactly when the user activates the consent checkbox or install button — the script sets mode to "screen" and flood-opacity to "1". With Cb=1 (white flood at full opacity), the screen formula immediately produces out=1: complete white-out at the instant of interaction.

<svg style="position:absolute;width:0;height:0">
  <defs>
    <filter id="msc005">
      <feFlood id="msc005Flood" flood-color="white" flood-opacity="0" result="fl"/>
      <feBlend id="msc005Blend" in="SourceGraphic" in2="fl" mode="normal"/>
    </filter>
  </defs>
</svg>
<div id="consent" style="filter:url(#msc005)">
  I agree to grant full file-system access to this MCP server.
</div>
<script>
document.getElementById("consent").addEventListener("mousedown", () => {
  document.getElementById("msc005Blend").setAttribute("mode", "screen");
  document.getElementById("msc005Flood").setAttribute("flood-opacity", "1");
  // screen(Cs, 1) = 1 for every channel; instant white-out at click time
});
</script>

The visual effect is identical to the lighten+white click-time attack (SA-CSS-BDL-004) and the color-dodge+white attack (SA-CSS-CDG-001): consent text is visible before the click, then erased to white at the moment of activation. All three attacks require separate per-mode detection strings because the mode attribute values "lighten", "color-dodge", and "screen" are distinct. A MutationObserver watching for mode attribute changes on feBlend nodes within consent ancestor filters must monitor these changes regardless of the new mode value.

Three modes, one visual effect, three detection entries: feBlend mode=lighten+white, mode=color-dodge+white, and mode=screen+white all produce out=1 at click time. Each requires a separate detection entry. No formula shortcut allows one rule to cover all three. Runtime MutationObserver on feBlend mode attribute changes in consent filter graphs is required.

SA-CSS-MSC-006: Cross-mode evasion — multiply+black → screen+white cycle

This dual-mode attack exploits the opposite extreme outputs of multiply and screen at opposite Cb extremes. Initial state: mode="multiply", in2 from a feFlood with flood-color="black" (Cb=0). Multiply formula: Cs×0=0. Output is black. On a dark-mode background, the consent text is invisible — dark-out. On mousedown: mode switches to "screen" and flood-color switches to "white" (Cb=1). Screen formula: Cs+1−Cs=1. Output is white. On a light-mode or white background, the consent text is invisible — white-out. The consent text is never visible in either state.

<svg style="position:absolute;width:0;height:0">
  <defs>
    <filter id="msc006">
      <feFlood id="msc006Flood" flood-color="black" flood-opacity="1" result="fl"/>
      <feBlend id="msc006Blend" in="SourceGraphic" in2="fl" mode="multiply"/>
    </filter>
  </defs>
</svg>
<div id="consent2" style="filter:url(#msc006)">
  I agree to grant full file-system access to this MCP server.
</div>
<script>
document.getElementById("consent2").addEventListener("mousedown", () => {
  // Before: mode=multiply, Cb=0 → out=0 (black); invisible on dark bg
  document.getElementById("msc006Blend").setAttribute("mode", "screen");
  document.getElementById("msc006Flood").setAttribute("flood-color", "white");
  // After: mode=screen, Cb=1 → out=1 (white); invisible on light bg
  // Consent text is never visible in either state
});
</script>

This attack is particularly resilient because it is adaptive: the two invisible states target different background themes. If the MCP installer uses a dark-mode UI, the pre-click dark-out hides the text. If the installer uses a light background for the consent confirmation page, the post-click white-out hides the text when the click is processed. The attacker covers both cases in a single filter configuration by exploiting the fact that multiply and screen have opposite extreme outputs at opposite ends of the Cb range.

Both states are invisible: multiply+black produces dark-out (invisible on dark UI). screen+white produces white-out (invisible on light UI). The consent text has zero opportunity for visibility before or during the click. Static analysis of the initial DOM finds mode=multiply+black (dark-out, CRITICAL). Dynamic analysis at click time finds mode=screen+white (white-out, CRITICAL). Both detections are independently required.

multiply vs darken and screen vs lighten — boundary equivalence

Understanding the precise conditions under which mode pairs produce identical outputs is necessary for writing complete detection coverage without duplicate logic.

ComparisonCb valuedarken/lighten resultmultiply/screen resultEqual?
multiply vs darken0 (black)min(Cs,0)=0Cs×0=0Yes — both 0
multiply vs darken0.5min(0.8,0.5)=0.50.8×0.5=0.4No — multiply darker
multiply vs darken1 (white)min(Cs,1)=CsCs×1=CsYes — both identity
screen vs lighten0 (black)max(Cs,0)=CsCs+0-0=CsYes — both identity
screen vs lighten0.5max(0.2,0.5)=0.50.2+0.5-0.1=0.6No — screen lighter
screen vs lighten1 (white)max(Cs,1)=1Cs+1-Cs=1Yes — both 1

The equivalences at boundary Cb values (0 and 1) explain why all four modes can produce identical consent-erasure attacks at those extremes, but different behavior at intermediate Cb means their detection conditions diverge for near-black/near-white thresholding.

Attack summary

IDModein2 CbFormula result (dark text Cs=0.04)EffectSeverity
SA-CSS-MSC-001multiply0 (black)0.04×0=0Dark-out (≡darken+black)Critical
SA-CSS-MSC-002screen1 (white)0.04+1−0.04=1White-out (≡lighten+white)Critical
SA-CSS-MSC-003screen0.94 (near-white)0.94+0.06×0.04=0.9424Near-white-out; WCAG 1.058:1High
SA-CSS-MSC-005normal→screen (mousedown)0→1identity→1Click-time white-outCritical
SA-CSS-MSC-006multiply→screen cycle0→10→1Dual-mode dark/white cycleCritical

Defences

SkillAudit findings for this attack surface

CRITICAL SA-CSS-MSC-001: feBlend mode="multiply" with feFlood flood-color near-black (all channels≤0.05 linear) — formula Cs×Cb; at Cb=0, Cs×0=0 for all source channels; complete dark-out regardless of source color; mathematically equivalent to mode="darken"+black but via different code path; detection list must include "multiply" separately from "darken".
CRITICAL SA-CSS-MSC-002: feBlend mode="screen" with feFlood flood-color white (Cb=1) — formula Cs+1−Cs=1; all channels raised to 1; white-out; equivalent to mode="lighten"+white (both output 1 at Cb=1) but different code path; separate "screen" detection entry required; dark consent text of any hue → rgb(255,255,255); invisible on white/light background.
HIGH SA-CSS-MSC-003: feBlend mode="screen" with near-white feFlood (Cb≥0.90, e.g. #f0f0f0, #ebebeb) — formula output = Cb + Cs×(1−Cb); dark text Cs=0.04: output≈Cb+0.06×0.04≈Cb; floor-raises all dark text to near-white; WCAG contrast vs white <1.1:1; evades exact-white flood-color string detection; threshold: any feFlood channel≥0.88 in screen blend context with dark foreground text.
CRITICAL SA-CSS-MSC-005: JS interaction handler switches feBlend mode from "normal" (identity) to "screen" and simultaneously sets flood-opacity from 0 to 1 (white) — initial state is transparent/identity; click-time: screen(Cs,1)=1; white-out at moment of consent activation; MutationObserver on feBlend mode attribute and feFlood flood-opacity within consent ancestor filter required.
CRITICAL SA-CSS-MSC-006: Dual-mode dark/white cycle — static state mode="multiply"+black (Cs×0=0; dark-out on dark UI); interaction state mode="screen"+white (Cs+1−Cs=1; white-out on light UI); consent text invisible in both states; exploits opposite extreme outputs of multiply and screen at opposite Cb endpoints; both states require independent detection; MutationObserver on both feBlend mode and feFlood flood-color attributes.

Related: feBlend darken/lighten consent attacks (SA-CSS-BDL)  |  feBlend mode=exclusion attacks  |  feBlend mode=overlay attacks  |  feBlend mode=luminosity attacks

← Blog  |  Security Checklist