← Blog

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.

By SkillAudit  |  24 Oct 2026

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

IDModein2 HSLOutput LEffectSeverity
SA-CSS-HUE-001hueS=0, L=1 (white)1Achromatic white-outCritical
SA-CSS-HUE-002hueS=0, L=0 (black)0Achromatic dark-outCritical
SA-CSS-HUE-003hueS=0, L=0.5 (gray)0.5Contrast collapse; WCAG ~3.95:1High
SA-CSS-HUE-004hue (mousedown)0→1 opacity, white0→1Click-time white-outCritical
SA-CSS-HUE-005hueS=0, any LL(in2)L-controlled achromatic collapseHigh

Defences

SkillAudit findings for this attack surface

CRITICAL SA-CSS-HUE-001: 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.
CRITICAL SA-CSS-HUE-002: 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.
HIGH SA-CSS-HUE-003: 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.
CRITICAL SA-CSS-HUE-004: JS interaction handler sets flood-opacity 0→1 with white feFlood on mode=hue — initial transparency yields normal render; click-time: output white; consent text erased at moment of activation; MutationObserver on feFlood flood-opacity in hue-mode filter graphs within consent ancestors required.

Related: Unified HSL feBlend detection (hue, saturation, color, luminosity)  |  feBlend mode=luminosity attacks  |  feBlend mode=saturation attacks

← Blog  |  Security Checklist