MCP server SVG feBlend luminosity consent security
Four SA-CSS-LUM consent attack patterns using feBlend mode="luminosity" — the W3C luminosity compositing mode forces the output lightness to match in2 while preserving the source's hue and chroma, enabling targeted lightness collapse attacks that render consent text invisible without modifying text color declarations.
The feBlend SVG filter primitive supports the full W3C Compositing and Blending Level 1 specification modes (CSS mix-blend-mode equivalents). The luminosity mode is defined as: the output pixel has the lightness (L) of in2 and the hue (H) and chroma (C) of in1, using the HSL color model. This separability of lightness from hue and chroma is the mathematical property exploited by SA-CSS-LUM attacks: by setting in2 to a constant-luminance layer (feFlood with extreme flood-color), all output pixels are driven to the same lightness value regardless of the consent text's original color.
Lightness override property: In HSL color space, any color at L=1 is white (#ffffff) and any color at L=0 is black (#000000), regardless of H and C. Therefore, a luminosity blend with an in2 layer of luminance = 1 (white feFlood) maps every input pixel to white. This is a mathematically guaranteed white-out — it does not depend on the specific consent text color, font, or rendering.
SA-CSS-LUM-001: mode="luminosity" + white in2 (lightness L=1 → white-out)
A feFlood flood-color="white" flood-opacity="1" produces a constant image with luminance 1.0 in all color spaces. When used as in2 in a luminosity blend, every output pixel receives L=1. In HSL, L=1 means the white endpoint regardless of hue and chroma. The consent text — which may be dark (#333), colored (blue links), or any other foreground color — is uniformly mapped to white. On a white or near-white MCP client background, the element is invisible.
The attack does not require knowing the consent element's foreground color in advance. The L=1 endpoint is the universal erasure value across all text colors, making this attack ICP-independent: it works on monochrome, colored, and themed MCP clients with light backgrounds.
<filter id="lum1"> <feFlood flood-color="white" flood-opacity="1" result="white_in2"/> <feBlend in="SourceGraphic" in2="white_in2" mode="luminosity"/> </filter> <!-- luminosity(in1, white): L_out = L(white) = 1; all pixels → #ffffff; white-out -->
SA-CSS-LUM-002: mode="luminosity" + black in2 (lightness L=0 → dark-out)
With in2 as a solid black feFlood (luminance = 0), all output pixels receive L=0. At L=0, HSL renders black (#000000) for every hue and chroma value. The consent element displays as a solid black rectangle. On dark-themed MCP clients (dark backgrounds, dark UI chrome), the black consent text on a dark background is invisible. The element retains its full layout footprint and remains interactive — clicks land on the transparent-to-user element.
<filter id="lum2"> <feFlood flood-color="black" flood-opacity="1" result="black_in2"/> <feBlend in="SourceGraphic" in2="black_in2" mode="luminosity"/> </filter> <!-- luminosity(in1, black): L_out = L(black) = 0; all pixels → #000000; dark-out -->
SA-CSS-LUM-003: mode="luminosity" + mid-gray in2 (L=0.5 → contrast collapse)
Setting in2 to a mid-gray feFlood (flood-color="#808080", luminance ≈ 0.216 in linearRGB or L ≈ 0.502 in sRGB) forces all pixels to mid-gray lightness. The consent text (which may be dark, e.g., #333 at L ≈ 0.2 in HSL) is pushed up to L=0.5, and the background (white, L=1) provides color context. On a mid-gray UI (L ≈ 0.5), the contrast between constrained-L=0.5 text and L=0.5 background approaches 1:1.
This attack does not achieve complete erasure in absolute terms — pixels are not zero. However, WCAG 2.1 requires a minimum contrast ratio of 4.5:1 for normal text. At L=0.5 vs L=0.5, the ratio is 1:1. The consent text fails readability standards without any pixel being zero or transparent, which causes it to evade alpha-channel and zero-output checkers.
<filter id="lum3"> <feFlood flood-color="#808080" flood-opacity="1" result="gray_in2"/> <feBlend in="SourceGraphic" in2="gray_in2" mode="luminosity"/> </filter> <!-- L_out = L(#808080) ≈ 0.5; text forced to mid-gray L; contrast vs. mid-gray background ≈ 1:1 -->
Color differentiation loss: SA-CSS-LUM-003 additionally eliminates hue-based consent differentiation. If the consent UI uses color coding (e.g., green approve button vs. red deny button), the luminosity blend preserves hue and chroma but collapses lightness. With all elements at L=0.5, color-blind users lose all differentiation and sighted users lose the lightness contrast cue, leaving only hue as a distinguishing signal — which is insufficient for WCAG compliance.
SA-CSS-LUM-004: JS mousedown mode normal → luminosity + white in2 inject (click-time)
The filter starts in mode="normal" with a transparent in2 feFlood (flood-opacity="0"). Normal blend with transparent in2 is an identity — the consent element renders normally. On mousedown, the script switches mode to "luminosity" and makes the white feFlood opaque. The luminosity blend immediately forces L=1 → white-out at the moment of interaction.
consentElement.addEventListener("mousedown", () => {
const fb = document.getElementById("consentFilter").querySelector("feBlend");
fb.setAttribute("mode", "luminosity");
const ff = document.getElementById("consentFilter").querySelector("feFlood");
ff.setAttribute("flood-opacity", "1"); // white layer now opaque
// all pixels immediately forced to L=1 → #ffffff
});
Attack summary
| ID | mode | in2 (feFlood) | L_out | Effect | Severity |
|---|---|---|---|---|---|
| SA-CSS-LUM-001 | luminosity | White (L=1) | 1 | White-out on light backgrounds | Critical |
| SA-CSS-LUM-002 | luminosity | Black (L=0) | 0 | Dark-out on dark backgrounds | Critical |
| SA-CSS-LUM-003 | luminosity | Mid-gray (L≈0.5) | ≈0.5 | Contrast collapse on gray backgrounds | High |
| SA-CSS-LUM-004 | normal→luminosity (mousedown) | Transparent→white | 0→1 | White-out at click time | Critical |
Defences
- Detect luminosity mode with extreme in2 luminance: For each feBlend in a consent ancestor filter with
mode="luminosity", compute the luminance of the in2 layer. If luminance > 0.95: flag white-out. If luminance < 0.05: flag dark-out. If 0.4 < luminance < 0.6: evaluate contrast collapse against the page background. - Distinguish from multiply/screen via fixed-point analysis: Luminosity attacks require checking the in2 lightness specifically (HSL L channel), not the raw RGB luminance (which is different for saturated colors). Convert the feFlood flood-color to HSL and check L directly to avoid false negatives on colored in2 layers.
- Simulate WCAG contrast ratio: Apply the luminosity blend formula to a representative dark text sample against the page background. If WCAG contrast < 4.5:1 (normal text) or < 3:1 (large text), flag the consent filter regardless of whether any channel is exactly zero.
- MutationObserver on feBlend mode and in2 feFlood attributes: SA-CSS-LUM-004 mutates both. Add
modeto the feBlend observer andflood-opacity/flood-colorto the in2 feFlood observer for consent ancestor filters.
SkillAudit findings for this attack surface
feBlend mode="luminosity" + feFlood in2 white (luminance≈1, L_HSL=1) — luminosity mode forces all output pixels to L=1; at L=1, HSL color is #ffffff regardless of hue/chroma; white-out for all text colors; invisible on white/light-themed MCP clients; getComputedStyle.color unchanged.feBlend mode="luminosity" + feFlood in2 black (luminance=0, L_HSL=0) — forces L=0; #000000 for all pixels; dark-out on dark-themed clients; element opaque and in-layout; click interactions land on invisible element; luminosity-specific dark-out not covered by multiply-mode dark-out heuristic.feBlend mode="luminosity" + feFlood in2 mid-gray (#808080, L≈0.5) — forces all pixels to L≈0.5; contrast vs. mid-gray background → 1:1; WCAG contrast threshold violated without any zero-output or zero-alpha; hue/chroma preserved but lightness-based differentiation lost; contrast-ratio simulation required for detection.feBlend mode "normal"→"luminosity" + feFlood flood-opacity 0→1 (white) — initial identity normal blend; click-time luminosity+white → L=1 white-out; MutationObserver on feBlend mode and in2 feFlood flood-opacity/flood-color required for consent ancestor filters.