MCP server SVG feBlend color consent security
Five SA-CSS-COL consent attack patterns using feBlend mode="color" — the HSL-decomposition mode that takes hue and saturation from the source graphic and lightness from in2, producing white-out when in2 is white (L=1), dark-out when in2 is black (L=0), and contrast collapse with mid-gray in2; the inverse of mode=luminosity which takes L from the source rather than from the blend layer.
The feBlend SVG filter primitive's mode="color" is the third of the four HSL-decomposition blend modes. Where mode=hue takes H from in1 and S+L from in2, and mode=saturation takes S from in1 and H+L from in2, mode=color takes both H and S from in1 (the source, consent text) and takes L (Lightness) from in2 (the backdrop, feFlood).
The mode=color and mode=luminosity modes are mathematical duals: color takes (H,S) from source and L from backdrop; luminosity takes L from source and (H,S) from backdrop. For consent-text attacks, this duality means mode=color is the dangerous one when in2 is attacker-controlled: the attacker sets the luminance of the output by choosing the feFlood color's L value, overriding the original text luminance entirely.
color vs luminosity — which is the attack path: mode=color takes L from in2 (attacker-controlled feFlood) → attacker sets output luminance. mode=luminosity takes L from in1 (consent text) → output preserves source luminance. For L-collapse attacks via feFlood, mode=color is the primary attack mode. Mode=luminosity with achromatic in2 does not collapse L. This asymmetry requires separate detection logic for each mode.
HSL decomposition: how mode=color works
/* mode=color: 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_source, L_blend) /* H+S from source, L from backdrop */ /* The dual relationship: */ /* mode=color: output = (H_src, S_src, L_blend) ← L from in2 */ /* mode=luminosity: output = (H_blend, S_blend, L_src) ← L from in1 */ /* Attack cases with achromatic in2 */ in2=white → L_blend=1 → output at L=1; white regardless of H_src, S_src in2=black → L_blend=0 → output at L=0; black regardless of H_src, S_src in2=gray50 → L_blend=0.5 → output at L=0.5; contrast collapse /* For chromatic (saturated) in2 */ in2=red(H=0, S=1, L=0.5) → L_blend=0.5 → output at L=0.5; mid-tone collapse in2=blue(H=240, S=1, L=0.3) → L_blend=0.3 → output at L=0.3 /* L_blend controls the output luminance regardless of in2 hue/saturation */
The key property: mode=color uses only L from in2 to determine output luminance; H and S from in2 are discarded. This means any feFlood color — regardless of its hue or saturation — with a high-luminance L value produces a white-out attack. The attacker can use red, blue, yellow, or any chromatic feFlood with L≈1, and mode=color will produce a white-out. This makes the attack surface broader than modes that require achromatic in2.
SA-CSS-COL-001: feBlend mode="color" + white in2 (L=1) — white-out via L-from-in2
A feFlood with flood-color="white" has L=1. Mode=color: output (H_source, S_source, L=1). At L=1 in HSL, the color is white regardless of H and S. Consent text of any color becomes white. On a white background: invisible.
<filter id="col001"> <feFlood flood-color="white" flood-opacity="1" result="whiteFlood"/> <feBlend in="SourceGraphic" in2="whiteFlood" mode="color"/> </filter> <!-- HSL(white) = (*, *, 1); output L = 1 --> <!-- At L=1: color is white regardless of H_src and S_src --> <!-- Dark text (H≈0, S≈0, L≈0.1): output (0, 0, 1) = white --> <!-- Saturated blue text (H=220, S=0.8, L=0.4): output (220, 0.8, 1) = white --> <!-- All consent text colors → white; white-out attack --> <!-- Note: "color" in mode="color" refers to the HSL color component (H+S), --> <!-- not a general color effect; the name does not indicate safety -->
SA-CSS-COL-002: feBlend mode="color" + black in2 (L=0) — dark-out via L-from-in2
A feFlood with flood-color="black" has L=0. Mode=color: output L=0. At L=0 in HSL, the color is black regardless of H and S. On a dark-mode background: invisible.
<filter id="col002"> <feFlood flood-color="black" flood-opacity="1" result="blackFlood"/> <feBlend in="SourceGraphic" in2="blackFlood" mode="color"/> </filter> <!-- HSL(black) = (*, *, 0); output L = 0; black regardless of source --> <!-- Light text rgb(229,231,235): output = black; dark-out on dark UI -->
SA-CSS-COL-003: feBlend mode="color" + mid-gray in2 (L=0.5) — L-collapse, WCAG fail
A feFlood with an achromatic gray having L≈0.5 produces output at L=0.5 for all source hues and saturations. WCAG contrast of L=0.5 (sRGB) text ≈ (1.05)/(0.216+0.05)=3.95:1 against white. Fails the 4.5:1 threshold. Any feFlood with L in the failure range — whether achromatic gray or a chromatic color at that L — produces the same consent-text contrast collapse.
<filter id="col003"> <feFlood flood-color="#808080" flood-opacity="1" result="grayFlood"/> <feBlend in="SourceGraphic" in2="grayFlood" mode="color"/> </filter> <!-- HSL(#808080) = (*, 0, 0.502 sRGB); output L = 0.502 sRGB ≈ 0.216 linear --> <!-- WCAG vs white: (1.05)/(0.216+0.05) ≈ 3.95:1 — FAIL --> <!-- Chromatic in2 with same L also attacks: --> <!-- feFlood flood-color="hsl(200, 60%, 50%)" → L=0.5 → same WCAG fail --> <!-- mode=color only uses L from in2, not H or S --> <!-- Detection: parse any feFlood color to L; check L range -->
Chromatic feFlood also attacks via mode=color: Unlike modes that require achromatic in2 for predictable attacks, mode=color uses only the L component of in2. A vivid red feFlood at L=1 (hsl(0,100%,100%)) still produces a white-out. A bright blue at L=0.8 still produces a near-white collapse. Any flood-color with high L is an attack vector for mode=color, regardless of saturation or hue.
SA-CSS-COL-004: JS mousedown flood-color → white — click-time color-mode white-out
Same click-time pattern as other HSL modes. Initial flood-opacity="0"; on mousedown, set flood-opacity="1" and flood-color="white". Mode=color with L(in2)=1 produces white output at the moment of consent activation.
<svg style="position:absolute;width:0;height:0">
<defs>
<filter id="col004">
<feFlood id="col004Flood" flood-color="white" flood-opacity="0" result="fl"/>
<feBlend in="SourceGraphic" in2="fl" mode="color"/>
</filter>
</defs>
</svg>
<div id="consent" style="filter:url(#col004)">
I agree to grant full file-system access to this MCP server.
</div>
<script>
document.getElementById("consent").addEventListener("mousedown", () => {
document.getElementById("col004Flood").setAttribute("flood-opacity", "1");
// color mode: output L=L(white)=1; white regardless of source H+S; white-out
});
</script>
SA-CSS-COL-005: mode=color vs mode=luminosity — the L-direction asymmetry
The most important distinction in the HSL mode family for consent-text security is the direction of L flow:
| Mode | L source | Attack via in2 control? | in2=white effect | in2=black effect |
|---|---|---|---|---|
| hue | in2 (backdrop) | Yes | L=1, white-out | L=0, dark-out |
| saturation | in2 (backdrop) | Yes | L=1, white-out | L=0, dark-out |
| color | in2 (backdrop) | Yes | L=1, white-out | L=0, dark-out |
| luminosity | in1 (source) | No (for L-collapse) | Output L = L(source text) | Output L = L(source text) |
Mode=luminosity takes L from the source (in1=consent text), not from in2. For achromatic in2, changing in2 from white to black does not change the output luminance — it remains L(source text). This is why mode=luminosity requires a different attack vector (non-achromatic in2 that shifts perceived luminance) rather than the direct L-from-in2 collapse that affects hue, saturation, and color modes.
This asymmetry matters for scanner design: a unified check that reads L(in2) and flags High/Critical applies correctly to hue, saturation, and color modes — but must NOT be applied to luminosity mode in the same way, or it will generate false positives. The luminosity mode requires a separate analysis path.
Attack summary
| ID | Mode | in2 L | Output L | Effect | Severity |
|---|---|---|---|---|---|
| SA-CSS-COL-001 | color | 1 (white) | 1 | White-out | Critical |
| SA-CSS-COL-002 | color | 0 (black) | 0 | Dark-out | Critical |
| SA-CSS-COL-003 | color | 0.5 (gray) | 0.5 | Contrast collapse; WCAG ~3.95:1 | High |
| SA-CSS-COL-004 | color (mousedown) | 0→1 | 0→1 | Click-time white-out | Critical |
| SA-CSS-COL-005 | color (chromatic in2) | any >0.70 | L(in2) | White-out via any high-L flood-color | High |
Defences
- Add color to the feBlend mode detection list: The full list now includes hue, saturation, color, and luminosity as separate entries. mode=color is the most attack-permissive of the four because L(in2) fully controls output luminance regardless of in2 hue or saturation.
- L-from-in2 check for mode=color: When feBlend mode=color is detected in a consent ancestor filter, parse the feFlood flood-color to its L value (HSL or luminance equivalent). If L > 0.70, flag Critical (white-out). If L < 0.15, flag Critical (dark-out). If 0.30 < L < 0.70, flag High (contrast collapse) and simulate WCAG.
- Chromatic feFlood is not safe: Unlike hue and saturation modes where achromatic in2 is the primary concern, mode=color attacks apply to any feFlood color with the right L value. Do not skip detection because in2 has non-zero saturation. Parse L from whatever flood-color representation is used.
- Do NOT apply L(in2) check to mode=luminosity: Luminosity takes L from source (in1), not in2. Achromatic in2 with luminosity preserves source text luminance. Applying the same L(in2) threshold check to luminosity creates false positives.
- Unified HSL detection covers hue, saturation, and color: A single detection function that checks L(in2) can serve all three modes. See the unified detection page for the shared algorithm covering all four modes with mode-specific logic.
- Runtime MutationObserver: Monitor feFlood flood-color and flood-opacity within color-mode feBlend filter chains on consent ancestors. Any change producing L(in2) > 0.70 is a click-time white-out.
SkillAudit findings for this attack surface
feBlend mode="color" with feFlood flood-color=white (L=1) — output L=1; white regardless of source H and S; consent text invisible on white/light background; separate "color" detection entry required; also applies to any flood-color with L>0.85 regardless of saturation.feBlend mode="color" with feFlood flood-color=black (L=0) — output L=0; dark regardless of source H and S; dark-out on dark background; any flood-color with L<0.05 is equivalent.feBlend mode="color" with feFlood at L in [0.30, 0.65] — all source hues and saturations rendered at L(in2); WCAG fail; applies to both achromatic and chromatic feFlood colors at the same L; broader attack surface than hue/saturation modes which require S=0 for predictable L collapse.