MCP server SVG feColorMatrix type=saturate consent security
Four SA-CSS-SAT consent attack patterns using feColorMatrix type="saturate" — destroying color-coded consent differentiation via full desaturation, chaining with feComponentTransfer to map desaturated gray to near-transparent, and near-zero fractional values that evade exact-zero scanners.
The feColorMatrix SVG filter primitive transforms the color of each pixel using a matrix multiplication or a named operation. The type="saturate" variant applies a saturation-scaling matrix to the RGB channels, with values controlling the saturation scale factor: 0 produces full grayscale (zero chroma), 1 is identity (no change), and values outside [0,1] are specified but produce implementation-dependent results. The saturate matrix scales the chroma deviation from the luminance axis by the given factor, preserving luminance while varying color intensity.
Color-as-signal attack surface: MCP consent panels often use color to convey meaning — green highlighted acceptance terms, red warnings, blue-underlined legal links. When color is the primary distinguishing signal between consent options (e.g., green "Approve" vs. grey "Decline" buttons), saturate=0 converts both to identical grayscale, making the consent semantically ambiguous. This attack passes standard WCAG luminance-contrast audits because desaturation preserves luminance, and scanners checking getComputedStyle see only the pre-filter CSS colors.
SA-CSS-SAT-001: type="saturate" values="0" — color differentiation collapse
With type="saturate" values="0", the SVG filter applies the full desaturation matrix: each RGB channel of each pixel is replaced with the pixel's luminance value (L = 0.2126R + 0.7152G + 0.0722B). A green consent text color (#22c55e, L ≈ 0.39) and a dark neutral text color (#374151, L ≈ 0.04) both become their respective gray equivalents — but the green text is now a medium gray (≈ #636363) while the dark text is near-black. More critically, consent panels that use color alone to label "Required consent" in green vs. "Optional preference" in gray lose all color differentiation between required and optional items: both appear in similar gray tones. WCAG audits on the desaturated output still report adequate luminance contrast, so this attack is invisible to automated accessibility checks.
<filter id="sat1"> <feColorMatrix type="saturate" values="0"/> </filter> <!-- saturate=0: converts all colors to luminance-equivalent gray; color-coded consent differentiation eliminated --> <div style="filter:url(#sat1)"> <span style="color:#22c55e">Required consent items</span> <span style="color:#9ca3af">Optional preferences</span> </div>
Not always an attack: For consent panels using only black text on white backgrounds, saturate=0 is an identity transform (black is already zero-chroma). Do not flag saturate=0 on elements where all consent text uses achromatic (gray/black/white) colors. Only flag when the target element contains chromatic colors that carry semantic meaning for consent.
SA-CSS-SAT-002: saturate=0 + feComponentTransfer gray-to-transparent chain
A more destructive two-primitive chain uses saturate=0 to convert all consent text to its grayscale equivalent, then a feComponentTransfer to map the resulting mid-gray luminance range to near-transparent alpha. After desaturation, typical consent text on a white background produces gray pixel values in the range 0.1–0.8 (luminance range). A feComponentTransfer with type="table" and tableValues="0 0 0 0 0 0 0 0 0.05 0.02 0.01 0" on the alpha channel maps the mid-range gray values (0.3–0.8) to alpha ≈ 0.01–0.05 — near-transparent. The background (luminance=1.0) maps to alpha=0, and pure black (luminance=0) also maps to alpha=0. Each primitive appears benign in isolation: the saturate transform preserves all content, and the feComponentTransfer operates on a grayscale image where no consent-specific targeting is visible.
<filter id="sat2">
<feColorMatrix type="saturate" values="0" result="gray"/>
<feComponentTransfer in="gray">
<!-- map mid-gray range (desaturated text) to near-zero alpha -->
<feFuncA type="table" tableValues="0 0 0 0.05 0.02 0.01 0 0 0 0 0"/>
</feComponentTransfer>
</filter>
<!-- two-primitive chain: saturate→gray, then feFuncA maps mid-gray alpha range to near-zero -->
SA-CSS-SAT-003: values="0.001" — near-zero saturation evades exact-zero check
With values="0.001", the saturation scale is 0.001 — the chroma component is scaled to 0.1% of its original value. The visual output is nearly indistinguishable from full desaturation (values=0): a fully saturated green (#22c55e) at saturation=0.001 retains only 0.001 of its chroma deviation from the luminance axis, producing a gray with trace color of approximately #636360 — less than 0.1% hue deviation from pure gray. Color-coded consent differentiation is equally destroyed. However, scanners that check for the exact string values="0" or a numeric equality test values == 0 will not match this value. The attack is functionally equivalent to SA-CSS-SAT-001 while evading signature-based detection.
<filter id="sat3"> <feColorMatrix type="saturate" values="0.001"/> </filter> <!-- values=0.001: 99.9% chroma reduction; visually identical to full desaturation; evades values==0 signature -->
SA-CSS-SAT-004: JS mousedown values injection (1 → 0)
Initially, the feColorMatrix uses values="1" — the identity saturation (no change). The consent panel displays with full color differentiation intact. On mousedown, the script changes values to "0", instantly stripping all color from the consent panel at the moment the user clicks. For UIs where the user is expected to click a color-coded consent acceptance button, the attack fires precisely as the click interaction begins, removing the color signal that guided the user's decision while still registering the click on the element.
consentEl.addEventListener("mousedown", () => {
document.querySelector("#consentFilter feColorMatrix")
.setAttribute("values", "0"); // strip color at mousedown
});
Attack summary
| ID | values | Mechanism | Effect | Severity |
|---|---|---|---|---|
| SA-CSS-SAT-001 | 0 | Full desaturation; color-coded differentiation collapse | Color meaning destroyed; WCAG passes | High |
| SA-CSS-SAT-002 | 0 + feComponentTransfer | Saturate→gray; feFuncA maps mid-gray to near-transparent | Near-transparent output for typical consent text | Critical |
| SA-CSS-SAT-003 | 0.001 | Functionally full desaturation; evades exact-zero check | Same as values=0; not flagged by signature scanners | High |
| SA-CSS-SAT-004 | 1 → 0 (mousedown) | Click-time saturation strip | Color stripped at interaction moment | High |
Defences
- Check element color usage before flagging: Detect whether the consent element contains chromatic colors (non-zero chroma in HSL). If all consent text uses achromatic colors, saturate=0 is not an attack. If any consent text is chromatic, flag saturate values < 0.1 as high-risk.
- Evaluate multi-primitive chains: A saturate primitive followed by feComponentTransfer with non-monotone alpha transfer functions is a two-step consent erasure pattern. Simulate the full chain output rather than analyzing each primitive independently.
- Use numeric comparison with epsilon: Saturation values in [0, 0.1) are effectively near-zero saturation. Use a threshold check (values < 0.1) rather than an exact-zero signature (values == 0) to catch SA-CSS-SAT-003-class attacks.
- MutationObserver on feColorMatrix values: SA-CSS-SAT-004 changes values at runtime. Observe the values attribute of feColorMatrix elements with type=saturate in consent ancestor filters. Any post-load change to values < 0.1 on an element with chromatic consent text is a flag.
SkillAudit findings for this attack surface
feColorMatrix type="saturate" values="0" — full desaturation; all chromatic consent text converted to grayscale equivalents; color-coded consent differentiation (required vs optional, accept vs decline) eliminated; luminance contrast unchanged so WCAG audits pass; only detected by analyzing whether color carries semantic meaning in the consent element.feColorMatrix type="saturate" values="0" followed by feComponentTransfer feFuncA with mid-gray range mapped to near-zero alpha — two-primitive chain; saturate converts colored text to mid-gray luminance values; feFuncA maps those mid-gray values to alpha ≈ 0.01–0.05; output is near-transparent for typical consent text colors; each primitive appears benign independently.feColorMatrix type="saturate" values="0.001" — chroma reduced to 0.1% of original; visually identical to full desaturation; color-coded consent differentiation equally destroyed; evades exact-values=="0" string or numeric equality signature; requires threshold-based detection (values < 0.1).feColorMatrix values from "1" to "0" — initial state consent text fully saturated; color stripped at mousedown; click registered on element while color signal removed; MutationObserver on values attribute of feColorMatrix type=saturate elements in consent ancestor filters required.