MCP server SVG feBlend hue consent security
Five SA-CSS-HUE consent attack patterns using feBlend mode="hue" — the HSL-decomposition mode that takes the hue component from the source graphic and the saturation and lightness from the blend layer (in2), producing achromatic white-out when in2 is white (L=1), dark-out when in2 is black (L=0), and complete contrast collapse when in2 is achromatic gray (L=0.5) regardless of the original consent text color or saturation.
The feBlend SVG filter primitive's mode="hue" is one of four HSL-decomposition blend modes defined in the W3C Compositing and Blending Level 1 specification (along with saturation, color, and luminosity). These modes decompose colors into their HSL components — Hue, Saturation, and Lightness — and recombine components from the two input layers according to the mode's rule.
For mode="hue", the rule is: the output color takes its Hue from in1 (the source graphic, Cs) and its Saturation and Lightness from in2 (the backdrop, Cb). In SVG filter graph terms: in1="SourceGraphic" (the consent text element) and in2 is the feFlood output. Therefore, the output Saturation = S(in2) and the output Lightness = L(in2), regardless of what the consent text actually looks like.
HSL hue mode: L and S fully controlled by in2: For mode=hue, the attacker controls the output luminance and saturation by choosing the feFlood color. Setting in2 to any achromatic color (S=0) forces output S=0 (desaturated). Setting in2 to a high-luminance color forces high output L. This gives the attacker complete control over readability regardless of source text color.
HSL decomposition: how mode=hue works
/* mode=hue: compose color from two HSL decompositions */ HSL(in1) = (H_source, S_source, L_source) /* source: consent text */ HSL(in2) = (H_blend, S_blend, L_blend) /* backdrop: feFlood */ output_HSL = (H_source, S_blend, L_blend) /* H from source, S+L from backdrop */ /* Convert back to RGB for rendering */ output_RGB = HSL_to_RGB(H_source, S_blend, L_blend) /* Attack cases */ in2=white → HSL(white)=(undefined, 0, 1) → output(H_src, 0, 1) = white (L=1, S=0) in2=black → HSL(black)=(undefined, 0, 0) → output(H_src, 0, 0) = black (L=0, S=0) in2=gray50 → HSL(gray)=(undefined, 0, 0.5) → output(H_src, 0, 0.5) = mid-gray /* Key insight: achromatic in2 (S=0) always produces achromatic output */ /* because output_S = S(in2) = 0 regardless of H_source or S_source */
The fundamental property of mode=hue with achromatic in2 is that the output is always achromatic (S=0) at the luminance of in2. Whether the consent text is dark blue, dark green, dark red, or dark gray — all produce the same achromatic output color determined entirely by the feFlood's luminance component. The source hue is preserved in name but renders identically because achromatic colors are hue-independent.
SA-CSS-HUE-001: feBlend mode="hue" + white in2 (L=1, S=0) — achromatic white-out
A feFlood with flood-color="white" has HSL = (undefined, 0, 1). Mode=hue takes H from in1 (consent text) and S=0, L=1 from in2 (white). The output has S=0 and L=1 regardless of the consent text hue or original luminance — achromatic white. On a white background, the consent text becomes white-on-white: invisible.
<filter id="hue001"> <feFlood flood-color="white" flood-opacity="1" result="whiteFlood"/> <feBlend in="SourceGraphic" in2="whiteFlood" mode="hue"/> </filter> <!-- HSL(white) = (*, 0, 1); output H=H(text), S=0, L=1 --> <!-- L=1 → maximum lightness; achromatic white regardless of source hue --> <!-- Dark text rgb(26,26,26): H≈undefined (near-black), output = white --> <!-- Blue text rgb(30,60,120): H≈220°, output = white (H preserved but invisible) --> <!-- Any foreground color → white; consent text invisible on white/light background -->
Hue is preserved but irrelevant: mode=hue with white in2 "preserves" the source hue in the output's H component, but since L=1 and S=0, the color is achromatic white — hue has no visible effect at L=1. The mathematical preservation of H is a technical truth that has zero perceptual consequence. The consent text is white regardless of original color.
SA-CSS-HUE-002: feBlend mode="hue" + black in2 (L=0, S=0) — achromatic dark-out
A feFlood with flood-color="black" has HSL = (undefined, 0, 0). Mode=hue output: H=H(text), S=0, L=0 — achromatic black. On a dark-mode UI background, the consent text becomes black-on-black: invisible.
<filter id="hue002"> <feFlood flood-color="black" flood-opacity="1" result="blackFlood"/> <feBlend in="SourceGraphic" in2="blackFlood" mode="hue"/> </filter> <!-- HSL(black) = (*, 0, 0); output H=H(text), S=0, L=0 --> <!-- L=0 → minimum lightness; achromatic black regardless of source hue --> <!-- Dark-mode UI (background #121212): consent text rgb(0,0,0) invisible --> <!-- Light text rgb(229,231,235): H≈0, output = black; dark-out on dark bg -->
SA-CSS-HUE-003: feBlend mode="hue" + achromatic gray in2 (L=0.5, S=0) — mid-luminance contrast collapse
A feFlood with an achromatic gray (e.g., flood-color="#808080") has HSL = (undefined, 0, L_gray). Mode=hue output: H=H(text), S=0, L=L_gray. All consent text — regardless of original color — renders as mid-gray. WCAG contrast of mid-gray (L≈0.216 linearised, sRGB 0.502) against white = (1+0.05)/(0.216+0.05)=1.05/0.266≈3.95:1 — below the 4.5:1 threshold. Darker gray achromatic in2 can push this further below threshold.
<filter id="hue003"> <feFlood flood-color="#808080" flood-opacity="1" result="grayFlood"/> <!-- HSL(#808080) = (undefined, 0, 0.502 sRGB) --> <!-- Linearised WCAG L ≈ 0.216 --> <feBlend in="SourceGraphic" in2="grayFlood" mode="hue"/> </filter> <!-- All source hues → output (H_src, 0, 0.502) = achromatic mid-gray --> <!-- WCAG contrast vs white: (1.05)/(0.216+0.05) ≈ 3.95:1 — FAIL for body text --> <!-- For darker gray in2 (L=0.3): WCAG ≈ (1.05)/(0.09+0.05)=7.5:1 — PASS (no attack) --> <!-- Attack range: achromatic in2 with L in [0.35, 0.85] → WCAG fail against white --> <!-- feFlood flood-color not "white" or "black" — evades endpoint string matching -->
SA-CSS-HUE-004: JS mousedown flood-color → white — click-time HSL white-out
The initial feFlood has flood-opacity="0" or a safe dark color, making the hue blend unattacked. On mousedown, the script sets flood-color to "white" and flood-opacity to "1". At L=1, S=0 from white in2, the mode=hue output collapses to achromatic white at the instant of consent activation.
<svg style="position:absolute;width:0;height:0">
<defs>
<filter id="hue004">
<feFlood id="hue004Flood" flood-color="white" flood-opacity="0" result="fl"/>
<feBlend in="SourceGraphic" in2="fl" mode="hue"/>
</filter>
</defs>
</svg>
<div id="consent" style="filter:url(#hue004)">
I agree to grant full file-system access to this MCP server.
</div>
<script>
document.getElementById("consent").addEventListener("mousedown", () => {
document.getElementById("hue004Flood").setAttribute("flood-opacity", "1");
// Now in2 white with full opacity: output H=H(text), S=0, L=1 → white
// Consent text invisible at click time
});
</script>
SA-CSS-HUE-005: feBlend mode="hue" + any achromatic in2 — generalised L-control attack
The critical generalisation of the hue-mode attack family is that any achromatic feFlood color (S=0, any L) produces achromatic output at that L value. The attacker can choose any luminance level to achieve the desired visual attack: near-white for invisible-on-white, near-black for invisible-on-dark, or any mid-tone for a contrast-collapse that evades endpoint detection.
<!-- Achromatic in2 examples: all produce achromatic output at L(in2) --> <feFlood flood-color="white"/> <!-- L=1: white-out --> <feFlood flood-color="#f8f8f8"/> <!-- L≈0.97: near-white-out --> <feFlood flood-color="#e0e0e0"/> <!-- L≈0.88: high WCAG fail --> <feFlood flood-color="#aaaaaa"/> <!-- L≈0.44: mid-tone fail --> <feFlood flood-color="#808080"/> <!-- L≈0.22: WCAG ≈4:1 borderline --> <feFlood flood-color="black"/> <!-- L=0: dark-out --> <!-- Detection approach: for mode=hue, resolve in2 to HSL --> <!-- If S(in2) ≈ 0 (achromatic, threshold S<0.05): --> <!-- output_L = L(in2) --> <!-- if L(in2) > 0.70: flag white-out (invisible on white bg) --> <!-- if L(in2) < 0.15: flag dark-out (invisible on dark bg) --> <!-- if 0.30 < L(in2) < 0.65: simulate WCAG for consent element -->
Attack summary
| ID | Mode | in2 HSL | Output L | Effect | Severity |
|---|---|---|---|---|---|
| SA-CSS-HUE-001 | hue | S=0, L=1 (white) | 1 | Achromatic white-out | Critical |
| SA-CSS-HUE-002 | hue | S=0, L=0 (black) | 0 | Achromatic dark-out | Critical |
| SA-CSS-HUE-003 | hue | S=0, L=0.5 (gray) | 0.5 | Contrast collapse; WCAG ~3.95:1 | High |
| SA-CSS-HUE-004 | hue (mousedown) | 0→1 opacity, white | 0→1 | Click-time white-out | Critical |
| SA-CSS-HUE-005 | hue | S=0, any L | L(in2) | L-controlled achromatic collapse | High |
Defences
- Add hue to the feBlend mode detection list: The full enumeration must now include: darken, lighten, overlay, soft-light, hard-light, color-dodge, color-burn, exclusion, difference, multiply, screen, and the four HSL modes: hue, saturation, color, luminosity. Each HSL mode requires a separate entry because the mode strings differ even if attack patterns are related.
- HSL decomposition analysis for mode=hue: When feBlend mode=hue is detected in a consent ancestor filter graph, resolve in2 (feFlood) to HSL. The output L = L(in2). If L(in2) > 0.70, flag as white-out (Critical). If L(in2) < 0.15, flag as dark-out (Critical). If 0.30 < L(in2) < 0.70, flag as contrast-collapse (High) and compute WCAG ratio against the background.
- Detect achromatic in2 regardless of color representation: White, #fff, #ffffff, rgb(255,255,255), hsl(0,0%,100%), and similar are all achromatic at L=1. Parse flood-color to HSL; check S < 0.05 (effectively achromatic) and check L. Do not rely on flood-color string matching against "white" or "black".
- Runtime MutationObserver: Monitor feFlood flood-color and flood-opacity attribute changes within hue-mode feBlend filter chains on consent ancestors. A transition from opacity-0 to opacity-1 with achromatic high-luminance flood-color is a click-time white-out (SA-CSS-HUE-004).
- Unified HSL mode detection: Modes hue, saturation, color, and luminosity all share the property that achromatic in2 produces achromatic output at L(in2). A single L-based analysis covers all four modes. See the unified HSL detection page for the shared detection algorithm.
SkillAudit findings for this attack surface
feBlend mode="hue" with feFlood flood-color=white (S=0, L=1) — output L=1, S=0; achromatic white regardless of source hue; consent text invisible on white/light background; mode=hue must be enumerated separately from mode=luminosity even though both produce white at L(in2)=1.feBlend mode="hue" with feFlood flood-color=black (S=0, L=0) — output L=0, S=0; achromatic black regardless of source color; consent text invisible on dark background; dark-mode UI attack vector.feBlend mode="hue" with achromatic gray feFlood (S=0, 0.30<L<0.65) — all source hues collapse to single gray luminance; WCAG contrast depends on L(in2) and background; at L(in2)=0.5 sRGB, WCAG vs white ≈3.95:1 (fail); both dark and light text rendered at same L value; evades endpoint-only flood-color detection.