MCP server SVG feConvolveMatrix bias consent security
Four SA-CSS-CVB consent attack patterns using the feConvolveMatrix bias attribute — a post-convolution additive constant that forces all channels to white (bias=1), to black (bias=-1), or to uniform gray via zero-sum kernel + bias=0.5, with JS mousedown injection for click-time attacks.
The feConvolveMatrix filter primitive applies a kernel convolution to each pixel of the input image. After the kernel multiplication and summation, the SVG spec defines the output channel value as: out = sum(kernel × neighborhood) / divisor + bias. The bias attribute thus adds a constant to every output channel value of every output pixel. Since SVG channel values are clamped to [0, 1], bias values at or above 1 force the post-addition value to the maximum (white), and bias values at or below -1 force the minimum after clamping to 0 (black). This arithmetic property makes bias one of the simplest universal white-out and dark-out mechanisms in the SVG filter primitive set.
Additive clamp property: For bias=1: out = conv_result + 1 ≥ 1, and since the SVG spec clamps channel values to [0, 1], all output channels are exactly 1.0 (maximum white) regardless of the input or kernel. A single attribute change on feConvolveMatrix produces a reliable, mathematically guaranteed white-out of any element. No specific kernel design is required — even an identity kernel (kernelMatrix="0 0 0 0 1 0 0 0 0") with bias=1 produces white-out.
SA-CSS-CVB-001: bias=1 white-out with any kernel
With bias="1", every output pixel has all channels clamped to 1.0 regardless of the convolution result. The consent element renders as a solid white rectangle — indistinguishable from the page background on standard white-background pages. The attack works with any syntactically valid kernel, including an identity kernel that appears non-destructive in isolation. A scanner that evaluates the kernelMatrix and divisor for dangerous patterns (e.g., all-zero kernel) will not flag an identity kernel, missing the bias=1 white-out mechanism entirely. The element's DOM presence, textContent, BCR, and getComputedStyle.color are all unchanged.
<filter id="cvb1">
<!-- identity kernel: passes kernel analysis; bias=1 white-out bypasses kernel check -->
<feConvolveMatrix order="3"
kernelMatrix="0 0 0 0 1 0 0 0 0"
divisor="1"
bias="1"/>
</filter>
<!-- out = identity(src) + 1 ≥ 1 → clamped to 1.0 → white-out -->
SA-CSS-CVB-002: bias=-1 dark-out
With bias="-1", every output channel value is shifted down by 1. For any kernel result in [0, 1], the post-addition value is in [-1, 0], which clamps to 0 (black) after the SVG clamping step. The entire consent element renders as a solid black rectangle. On dark-background pages, this is visually absent (black on black). On white-background pages, it presents as a solid black bar — visually present but with the appearance of a redacted document, which may mislead users into believing the consent has been intentionally hidden rather than illegally obscured. Because the element is rendered black rather than transparent, alpha=0 and transparency checks pass.
<filter id="cvb2">
<feConvolveMatrix order="1"
kernelMatrix="1"
divisor="1"
bias="-1"/>
</filter>
<!-- out = src + (-1) ≤ 0 → clamped to 0.0 → dark-out (black) -->
Dark-out vs. transparent: SA-CSS-CVB-002 produces a black output, not a transparent one. Contrast against a dark page background may be near-zero. The element is visible (not invisible) on white backgrounds, making this a different consent-evasion mechanism: the user can see there is a consent panel but cannot read its content. This counts as a consent-obscuring attack even though alpha is 1.0.
SA-CSS-CVB-003: bias=0.5 with zero-sum kernel — uniform gray contrast collapse
A zero-sum kernel — where all coefficient values sum to zero — produces zero output for any uniform-color input region. This is the mathematical basis for edge-detection kernels (e.g., Laplacian: 0 -1 0 -1 4 -1 0 -1 0). Adding bias="0.5" to a zero-sum kernel makes all uniform regions render at exactly 0.5 (medium gray, #808080). The consent text body, which is rendered in uniform dark pixels, becomes medium gray. The white background, which is also uniform, becomes medium gray. The result is a medium-gray rectangle where both foreground (consent text interior) and background map to approximately the same value — contrast ratio ≈ 1:1. Only edge pixels (glyph outlines), where the kernel produces nonzero output due to luminance transitions, produce non-gray values — and those edges are too faint and fragmented for the consent text to be readable.
<filter id="cvb3">
<!-- Laplacian edge-detection kernel (sum=0) + bias=0.5: edge enhancement visual, consent gray-out effect -->
<feConvolveMatrix order="3"
kernelMatrix="0 -1 0 -1 4 -1 0 -1 0"
divisor="1"
bias="0.5"/>
</filter>
<!-- uniform regions → 0 + 0.5 = 0.5 (medium gray); text interior and background both → #808080; contrast ≈ 1:1 -->
SA-CSS-CVB-004: JS mousedown bias injection (0 → 1)
Initially, the feConvolveMatrix uses bias="0" (the SVG default) with a near-identity kernel. The consent element renders normally. On mousedown, the script changes bias to "1" simultaneously with switching the kernel to an all-zeros kernel (zero-sum, ensuring the convolution contribution is zero before bias addition). The effect is immediate white-out at the mousedown event. The consent text is readable until the click begins, then becomes white. Since the change occurs at mousedown (before click), the user reads the consent but at the moment of clicking, the text has already turned white — and any screenshot taken during the click event shows a white panel, making forensic analysis ambiguous.
consentEl.addEventListener("mousedown", () => {
const fc = document.querySelector("#consentFilter feConvolveMatrix");
fc.setAttribute("kernelMatrix", "0 0 0 0 0 0 0 0 0"); // zero kernel
fc.setAttribute("bias", "1"); // bias=1 white-out
});
Attack summary
| ID | bias value | kernel | Effect | Severity |
|---|---|---|---|---|
| SA-CSS-CVB-001 | 1 | Any (e.g., identity) | All channels → 1 (white-out); kernel appears benign | Critical |
| SA-CSS-CVB-002 | -1 | Any (e.g., passthrough) | All channels → 0 (dark-out); black rectangle | Critical |
| SA-CSS-CVB-003 | 0.5 | Zero-sum (Laplacian) | Uniform regions → medium gray; contrast ≈ 1:1 | High |
| SA-CSS-CVB-004 | 0 → 1 (mousedown) | Identity → all-zeros | White-out at click time; forensics shows white panel | Critical |
Defences
- Check bias range independently of kernel: Flag |bias| > 0.9 regardless of kernel content. Bias=1 is a guaranteed white-out and bias=-1 is a guaranteed dark-out regardless of what the kernel does. Do not gate this check on kernel analysis results.
- Evaluate zero-sum kernel + non-zero bias: Compute the sum of all kernelMatrix coefficients divided by divisor. If sum ≈ 0 and bias ≈ 0.5, the consent element will render as uniform medium gray. Flag this combination as a contrast-collapse pattern.
- Simulate rendered output: Apply the feConvolveMatrix (kernel + divisor + bias) to a sample dark-text-on-white input and compute WCAG contrast ratio of the output. Flag if contrast < 4.5:1 for normal text sizes.
- MutationObserver on bias attribute: SA-CSS-CVB-004 changes bias at runtime. Observe the bias attribute on feConvolveMatrix elements in consent ancestor filters. Any post-load change to |bias| > 0.9 is a critical flag.
SkillAudit findings for this attack surface
feConvolveMatrix bias="1" — post-convolution additive constant forces all channel outputs to ≥1; SVG [0,1] clamping produces all-white output; applies with any kernel including identity; kernel-only analysis misses this attack path; consent element renders as solid white rectangle; DOM unchanged.feConvolveMatrix bias="-1" — shifts all channels to ≤0; clamping produces all-black output; dark-out on any background; visually appears as redacted bar on white pages; alpha=1.0 so transparency checks pass; content fully illegible.feConvolveMatrix bias="0.5" with zero-sum kernel — uniform-color regions produce zero convolution output; adding bias=0.5 maps all uniform text interior and background pixels to #808080; contrast ≈ 1:1; only glyph edges produce non-gray output but are too sparse for readability; common edge-detection kernel pattern disguises attack intent.feConvolveMatrix bias from "0" to "1" + kernelMatrix to all-zeros — identity display until click; white-out at mousedown; screenshot of click event shows white panel; MutationObserver on bias attribute of feConvolveMatrix elements in consent ancestor filters required.