MCP server SVG feComposite arithmetic k-value consent security
Four SA-CSS-KAR consent attack patterns using feComposite operator="arithmetic" — the four-parameter formula out = k1×in1×in2 + k2×in1 + k3×in2 + k4 set to produce constant white-out, dark-out, or amplification-to-white regardless of the consent element's rendered pixels.
The feComposite SVG filter primitive composites two images. When operator="arithmetic", it applies the formula out = k1×in1×in2 + k2×in1 + k3×in2 + k4 per-pixel (SVG §15.8). The formula is a bilinear polynomial in in1 and in2 controlled by four scalar coefficients. Several configurations produce constant output independent of the source image: any setting where the in1 and in2 terms cancel out or are zeroed leaves only the constant term k4. This is the fundamental mechanism behind two of the four SA-CSS-KAR attack patterns.
Constant-output property: When k1=k2=k3=0, all input-dependent terms vanish. The formula reduces to out = k4 — a constant for all pixels. The value of in1 (the SourceGraphic, containing the consent text) is entirely ignored. Setting k4=1 produces white-out; setting k4=-1 (clamped to 0 per SVG spec §15.4) produces dark-out or full transparency.
SA-CSS-KAR-001: k1=k2=k3=0, k4=1 (constant white-out)
With all input-dependent coefficients zero and k4=1, every output pixel is clamped to 1. The consent element is completely white. No in2 primitive is required — the k3 term is zero so in2 is multiplied out. The filter can be written with a single feComposite primitive applied directly to SourceGraphic.
This is the most structurally minimal consent erasure possible: one filter element, one feComposite, four numeric attributes, no additional feFlood or chaining primitives. Static analysis tools that count filter complexity or require multiple primitives for flagging will miss it entirely.
<filter id="kar1">
<feComposite in="SourceGraphic" operator="arithmetic"
k1="0" k2="0" k3="0" k4="1"/>
</filter>
<!-- out = 0×in1×in2 + 0×in1 + 0×in2 + 1 = 1; constant white-out -->
SA-CSS-KAR-002: k1=k2=k3=0, k4=-1 (dark-out via clamp)
Setting k4=-1 (or any negative value) with all other coefficients zero produces a computed output of -1 per pixel. Per SVG spec §15.4, filter outputs are clamped to [0, 1]. The clamped result is 0. All RGB channels become 0 (black) — dark-out. Applied to an element with a dark UI theme, the consent text is invisible against the background.
If the filter chain applies the same constant-output feComposite to the alpha channel (or if premultiplied alpha processing causes the alpha to also be zeroed), the element becomes transparent rather than black. The exact behavior depends on the SVG filter color-interpolation-filters setting (linearRGB vs sRGB) and whether the renderer applies premultiplied alpha during compositing.
<filter id="kar2">
<feComposite in="SourceGraphic" operator="arithmetic"
k1="0" k2="0" k3="0" k4="-1"/>
</filter>
<!-- out = -1; clamped to 0 → dark-out; on dark-themed clients: text invisible -->
SA-CSS-KAR-003: k1=100, white feFlood in2, k2=k3=k4=0 (amplify+clamp white-out)
This variant uses the product term: out = k1 × in1 × in2 with a solid white feFlood as in2 (all channels = 1). The formula becomes out = 100 × in1 × 1 = 100 × in1. For any pixel where in1 > 0.01 — that is, any pixel with any non-trivial brightness — the output exceeds 1 and is clamped to 1. The result is that all non-black pixels become white. Anti-aliased text edges (which are always at intermediate luminance values) are reliably wiped. The consent element collapses to a pure white rectangle.
This attack is structurally different from SA-CSS-KAR-001: it uses a two-primitive chain (feFlood + feComposite) and the k1 product term rather than the constant k4 term, making it harder to detect with simple attribute-value checks. The apparent innocuousness of large numeric k1 values (they could legitimately be used for brightness amplification effects) provides additional evasion.
<filter id="kar3">
<feFlood flood-color="white" flood-opacity="1" result="white_in2"/>
<feComposite in="SourceGraphic" in2="white_in2" operator="arithmetic"
k1="100" k2="0" k3="0" k4="0"/>
</filter>
<!-- out = 100 × in1 × 1; any in1 > 0.01 → clamped to 1; all anti-aliased pixels → white -->
False positive note: k2=1, k1=k3=k4=0 is the identity for in1 (out = 1×in1). Do not flag k2=1 as an attack. The constant-output attacks require the in1 and in2 terms to both be zero — only k1=k2=k3=0 (for k4-based attacks) or extreme k1 with a saturated in2 (for amplification attacks) warrant flagging.
SA-CSS-KAR-004: JS mousedown changes k4 0 → 1 (click-time white-out injection)
The feComposite begins in identity configuration: k2=1, k1=k3=k4=0. The formula out = in1 passes SourceGraphic unchanged. The consent text is fully visible. On mousedown, the script sets k2="0" and k4="1", switching from identity to constant white-out. The transition is instantaneous — the mousedown event fires before the click completes.
consentElement.addEventListener("mousedown", () => {
const fc = document.getElementById("consentFilter")
.querySelector("feComposite[operator='arithmetic']");
fc.setAttribute("k2", "0"); // remove identity pass-through
fc.setAttribute("k4", "1"); // inject constant white-out
});
MutationObserver requirement: Static analysis reads k2="1" k4="0" — a valid identity configuration. Only runtime observation of k1, k2, k3, and k4 attribute mutations on feComposite operator="arithmetic" elements within consent ancestor filters will catch this attack.
Attack summary
| ID | k1 | k2 | k3 | k4 | in2 | Effect | Severity |
|---|---|---|---|---|---|---|---|
| SA-CSS-KAR-001 | 0 | 0 | 0 | 1 | — | Constant white-out | Critical |
| SA-CSS-KAR-002 | 0 | 0 | 0 | -1 | — | Dark-out (clamp to 0) | Critical |
| SA-CSS-KAR-003 | 100 | 0 | 0 | 0 | White feFlood | Amplify+clamp → white-out | High |
| SA-CSS-KAR-004 | 0 | 1→0 | 0 | 0→1 | — | Identity → white-out at mousedown | Critical |
Defences
- Check for constant output from arithmetic operator: For each
feComposite operator="arithmetic"in a consent ancestor filter, ifk1=k2=k3=0, compute output =k4. Ifk4 > 0.95: white-out; ifk4 ≤ 0: dark-out. Flag either. - Check for extreme k1 with saturated in2: If
k1 > 10andin2is a feFlood with luminance > 0.9, compute the effective in1 threshold at which clamping occurs:threshold = 1 / k1. If threshold < 0.1 (meaning 90%+ of pixels are white-out targets), flag as white-out by amplification. - Evaluate identity configurations for JS override risk: A
k2=1, k1=k3=k4=0configuration (identity) in a consent ancestor filter with any attached JavaScript should be flagged as a potential JS-override target. Recommend MutationObserver deployment. - MutationObserver on all k-value attributes: Monitor
k1,k2,k3, andk4onfeComposite operator="arithmetic"elements within consent ancestor filters.
SkillAudit findings for this attack surface
feComposite operator="arithmetic" k1="0" k2="0" k3="0" k4="1" — constant-output formula out=k4=1; white-out for all pixels regardless of source; single-primitive, no in2 required; evades multi-primitive complexity heuristics; most minimal feComposite consent attack.feComposite operator="arithmetic" k4="-1" (k1=k2=k3=0) — out=-1 clamped to 0; dark-out; on dark UI themes consent text black-on-black invisible; premultiplied alpha processing may additionally produce transparency; SVG §15.4 clamp behavior is the key mechanism.feComposite operator="arithmetic" k1="100" k2=0 k3=0 k4=0 + feFlood white in2 — product term amplification: out=100×in1×1; all pixels with in1>0.01 clamped to 1; anti-aliased text (always in1>0) → white; evades k2/k4 checks; large k1 with saturated in2 detection required.feComposite k2="1"→"0" k4="0"→"1" — initial identity configuration (out=in1); click-time constant white-out injection; MutationObserver on k1/k2/k3/k4 attributes of arithmetic feComposite in consent ancestor filters required.