MCP server SVG feBlend mode consent security
Four SA-CSS-FBM consent attack patterns using the feBlend mode attribute — multiply, screen, and overlay compositing with extreme in2 layers to produce dark-out, white-out, and contrast collapse — with JS mousedown mode injection for click-time attacks.
The feBlend SVG filter primitive composites two images using a blending mode. The mode attribute determines the formula applied per-pixel. SVG filter blending supports the same W3C compositing modes as CSS mix-blend-mode: normal, multiply, screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue, saturation, color, luminosity. Several of these have mathematical fixed-points that produce constant output regardless of the source (in1) value, making them effective erasure primitives when in2 is set to the appropriate constant value.
Fixed-point property: For any blend mode F(A, B) where B is a constant, if F(A, B) equals the same constant C for all values of A, then the blend is a constant-output filter regardless of what the source contains. This is the mathematical property exploited by SA-CSS-FBM-001 (multiply with black) and SA-CSS-FBM-002 (screen with white).
SA-CSS-FBM-001: feBlend mode="multiply" with black in2 layer
The multiply blend formula is C = A × B per channel. When in2 is a solid black layer (all channels = 0), every output channel is A × 0 = 0. The output is uniformly black regardless of the consent text color. On a dark-themed MCP client, black text on a black/dark background is invisible. The element retains its layout, textContent, and all computed CSS values. getComputedStyle.color continues to return the declared foreground color.
<filter id="fbm1"> <feFlood flood-color="black" flood-opacity="1" result="black_layer"/> <feBlend in="SourceGraphic" in2="black_layer" mode="multiply"/> </filter> <!-- multiply(any_color, 0) = 0 → all pixels black → dark-out on dark UIs -->
SA-CSS-FBM-002: feBlend mode="screen" with white in2 layer
The screen blend formula is C = 1 - (1-A)(1-B). When in2 is solid white (all channels = 1), the formula becomes 1 - (1-A)(1-1) = 1 - 0 = 1 for all values of A. Every output channel is 1 (white). The consent text is erased to white, invisible on white or light backgrounds. This is the most common feBlend consent attack in the SkillAudit dataset — the feFlood+feBlend screen pair appears frequently in community MCP skills with consent dialogs.
<filter id="fbm2"> <feFlood flood-color="white" flood-opacity="1" result="white_layer"/> <feBlend in="SourceGraphic" in2="white_layer" mode="screen"/> </filter> <!-- screen(any_color, 1) = 1 → all pixels white → white-out on light UIs -->
SA-CSS-FBM-003: feBlend mode="overlay" with mid-gray in2 layer
The overlay formula is piecewise: 2AB when B < 0.5, and 1 - 2(1-A)(1-B) when B ≥ 0.5. At the exact midpoint B = 0.5, both branches yield the same value: 2A×0.5 = A and 1 - 2(1-A)(0.5) = 1 - (1-A) = A. An exact mid-gray in2 is therefore an identity for overlay — it preserves the source. However, values slightly above or below 0.5 push the formula toward amplification (contrast increase for B > 0.5) or compression (contrast decrease for B < 0.5).
SA-CSS-FBM-003 uses a near-0.5 gray (e.g., flood-color=#7f7f7f ≈ 0.498) to shift all pixels toward a near-uniform gray value. With a dark text color (e.g., #333) and near-0.5 overlay, the output is compressed to a very narrow luminance range around 0.5. Against a mid-gray background, the contrast ratio approaches 1:1, making the consent text effectively invisible without any channel reaching zero.
<filter id="fbm3"> <feFlood flood-color="#7f7f7f" flood-opacity="1" result="gray_layer"/> <feBlend in="SourceGraphic" in2="gray_layer" mode="overlay"/> </filter> <!-- overlay(dark_text, ~0.5) ≈ 0.5 for all pixels → near-uniform mid-gray → 1:1 contrast on gray background -->
SA-CSS-FBM-004: JS mousedown mode injection (normal → screen)
The feBlend initially uses mode="normal" with a transparent in2 layer (feFlood with flood-opacity="0"). In normal mode, feBlend alpha-composites the two inputs; with a fully transparent in2, the result is identical to in1 — the consent text is rendered normally. On mousedown, the script changes mode to "screen" and simultaneously changes the in2 feFlood's flood-opacity to 1 (making it opaque white). The screen blend immediately produces white-out.
consentElement.addEventListener("mousedown", () => {
const fb = document.getElementById("consentFilter").querySelector("feBlend");
fb.setAttribute("mode", "screen");
const ff = document.getElementById("consentFilter").querySelector("feFlood");
ff.setAttribute("flood-opacity", "1"); // make white layer opaque
});
Mode-specific detection: The attack mode is not always screen. Flag: multiply with a low-luminance in2 layer (luminance < 0.05); screen with a high-luminance in2 layer (luminance > 0.95); overlay/soft-light/hard-light with in2 luminance near 0.5 (0.4–0.6) combined with a mid-gray background. Each mode requires a different fixed-point analysis.
Attack summary
| ID | mode | in2 layer | Formula fixed point | Effect | Severity |
|---|---|---|---|---|---|
| SA-CSS-FBM-001 | multiply | Black (all=0) | A×0=0 | Dark-out on dark UIs | Critical |
| SA-CSS-FBM-002 | screen | White (all=1) | 1-(1-A)(0)=1 | White-out on light UIs | Critical |
| SA-CSS-FBM-003 | overlay | Mid-gray (~0.5) | Output ≈ 0.5 for all A | Contrast collapse on gray background | High |
| SA-CSS-FBM-004 | normal→screen (mousedown) | Transparent→white | screen(A,1)=1 | White-out at click time | Critical |
Defences
- Evaluate feBlend fixed points: For each feBlend in a consent ancestor filter, compute the luminance of the in2 layer. If mode=multiply and luminance < 0.05: flag dark-out. If mode=screen and luminance > 0.95: flag white-out. If mode=overlay and 0.4 < luminance < 0.6: evaluate contrast collapse against the page background.
- Check in2 source: If in2 is a feFlood with extreme flood-color (black or white) and flood-opacity=1, flag unconditionally for multiply and screen modes respectively. This catches the most common attack pattern with zero computational overhead.
- Simulate output for edge cases: For modes like hard-light, soft-light, color-dodge, and color-burn, no simple luminance threshold suffices. Simulate the output for representative foreground-color samples and compute WCAG contrast against the background.
- MutationObserver on feBlend mode and in2 feFlood flood-opacity: SA-CSS-FBM-004 changes both. Add
modeto the observer for feBlend, andflood-opacityandflood-colorfor feFlood elements that are named in2 inputs of feBlend in consent ancestor filters.
SkillAudit findings for this attack surface
feBlend mode="multiply" + feFlood in2 black (luminance ≈ 0) — fixed point: multiply(A,0)=0; dark-out for all foreground colors; invisible on dark-themed MCP clients; getComputedStyle.color unchanged.feBlend mode="screen" + feFlood in2 white (luminance ≈ 1) — fixed point: screen(A,1)=1; white-out for all foreground colors; invisible on white/light-themed MCP clients; most frequently observed feBlend consent attack pattern.feBlend mode="overlay" + feFlood in2 near-0.5 gray — output compressed to near-0.5 for all input values; contrast ratio approaches 1:1 against mid-gray background; no channel reaches zero; evades zero-output and transparency checks.feBlend mode from "normal" to "screen" + feFlood flood-opacity 0→1 — initial state identity blend (consent text visible); white-out at click time; MutationObserver on mode attribute of feBlend and flood-opacity/flood-color of in2 feFlood elements required.