MCP server SVG feComponentTransfer discrete consent security
Four SA-CSS-DIS consent attack patterns using feComponentTransfer type="discrete" — nearest-neighbor quantization from tableValues bins exploited to erase all alpha with a single-bin map, white-out RGB channels, and generate 1-bit alpha checkerboard patterns that destroy consent text readability without triggering standard transparency detectors.
The feComponentTransfer type="discrete" transfer function applies nearest-neighbor quantization per channel. For N values in tableValues, the domain [0, 1] is divided into N equal bins of width 1/N. An input value in bin k (where k = clamp(floor(input × N), 0, N−1)) maps to tableValues[k]. Unlike type="table" (piecewise-linear interpolation), discrete produces step-function output — no interpolation between breakpoints. This makes discrete attacks more uniform within each bin and cleaner at bin boundaries, and it enables 1-bit quantization attacks on the alpha channel that produce structured spatial patterns.
Single-bin collapse: With N=1 (a single value in tableValues), the entire domain [0, 1] falls in bin 0, and all inputs map to tableValues[0]. Setting tableValues="0" on feFuncA maps every alpha value to 0 — the most minimal SVG filter transparency attack: one attribute, one value, zero additional primitives. No multi-primitive chain, no feFlood, no arithmetic operator.
SA-CSS-DIS-001: feFuncA tableValues="0" (single-bin full transparency)
A single-value tableValues="0" creates one bin covering [0, 1]. Every alpha value, regardless of its input, maps to 0. The element is fully transparent. This is the minimal discrete consent erasure attack: a single feFuncA element with one attribute value. The filter structure — a feComponentTransfer with one feFuncA child — is structurally identical to a legitimate brightness/contrast filter, making structural analysis uninformative.
<filter id="dis1">
<feComponentTransfer>
<feFuncA type="discrete" tableValues="0"/>
</feComponentTransfer>
</filter>
<!-- N=1: bin [0,1] → tableValues[0]=0; all alpha → 0; element fully transparent -->
SA-CSS-DIS-002: feFuncR/feFuncG/feFuncB tableValues="1 1 1 1 1" (5-bin white-out)
Five bins all mapping to 1: inputs in [0, 0.2), [0.2, 0.4), [0.4, 0.6), [0.6, 0.8), and [0.8, 1.0] all map to 1. Every RGB channel value quantizes to 1. The element is white. The 5-bin encoding is functionally equivalent to the 1-bin tableValues="1" but has more entries, increasing apparent complexity. Analyzers that only flag 1-element tableValues may miss 5-element all-constant tables. The constant-output check must be generalized: if all tableValues elements equal the same value, the function is constant.
<filter id="dis2">
<feComponentTransfer>
<feFuncR type="discrete" tableValues="1 1 1 1 1"/>
<feFuncG type="discrete" tableValues="1 1 1 1 1"/>
<feFuncB type="discrete" tableValues="1 1 1 1 1"/>
</feComponentTransfer>
</filter>
<!-- 5 bins all → 1.0; all RGB channels → 1; white-out -->
SA-CSS-DIS-003: feFuncA tableValues="0 1 0 1 0 1 0 1 0 1" (10-bin checkerboard alpha)
Ten alternating bins create a 1-bit alpha quantization: input alpha in even-index bins (0, 2, 4, 6, 8) maps to 0 (transparent); input alpha in odd-index bins (1, 3, 5, 7, 9) maps to 1 (opaque). Each bin covers 0.1 of the alpha range. Anti-aliased text edges have alpha values spanning the full [0, 1] range; the alternating quantization places adjacent text-edge pixels in alternating transparent/opaque states.
The visual result is a checkerboard pattern across the glyph: every other alpha band is erased. The pattern is spatially periodic with a frequency determined by the number of bins and the local gradient of the anti-aliased alpha field. Text rendered with this filter is illegible — glyphs appear as fragmented patterns of alternating visibility rather than smooth shapes. Unlike SA-CSS-DIS-001 (complete transparency), some pixels remain visible, which allows the attack to pass threshold checks that require “at least N% of pixels are opaque”.
<filter id="dis3">
<feComponentTransfer>
<feFuncA type="discrete" tableValues="0 1 0 1 0 1 0 1 0 1"/>
</feComponentTransfer>
</filter>
<!-- bins 0,2,4,6,8 → alpha=0; bins 1,3,5,7,9 → alpha=1;
anti-aliased text: adjacent pixels alternate transparent/opaque → checkerboard -->
Checkerboard evasion mechanism: Average alpha over the consent element under SA-CSS-DIS-003 is approximately 0.5 (half the bins are opaque). Coarse detectors that compute mean alpha and flag near-zero will find 0.5 — well above threshold. Detection requires either simulating the rendered output on a test canvas, checking for high-frequency oscillation in tableValues (alternating 0/1 pattern), or using OffscreenCanvas to measure actual pixel luminance after filter application.
SA-CSS-DIS-004: JS mousedown changes feFuncA tableValues "1" → "0" (click-time erase)
The filter begins with feFuncA type="discrete" tableValues="1" — a single-bin identity-maximum: all alpha values map to 1 (fully opaque). Every pixel in the consent element is maximally visible. On mousedown, the script changes tableValues to "0" — single-bin all-transparent. The switch from fully-opaque to fully-transparent is instantaneous at the pixel renderer level.
consentElement.addEventListener("mousedown", () => {
const fa = document.getElementById("consentFilter")
.querySelector("feFuncA[type='discrete']");
fa.setAttribute("tableValues", "0"); // all-transparent: 1 bin → 0
// element invisible at click time; click lands on transparent element
});
Attack summary
| ID | Channel | tableValues | Bins | Effect | Severity |
|---|---|---|---|---|---|
| SA-CSS-DIS-001 | A | "0" | 1 all-zero | All alpha → 0 → transparent | Critical |
| SA-CSS-DIS-002 | R, G, B | "1 1 1 1 1" | 5 all-one | All RGB → 1 → white-out | Critical |
| SA-CSS-DIS-003 | A | "0 1 0 1 0 1 0 1 0 1" | 10 alternating | 1-bit alpha checkerboard → unreadable | High |
| SA-CSS-DIS-004 | A (mousedown) | "1" → "0" | 1 opaque→transparent | Fully opaque → transparent at click time | Critical |
Defences
- Detect constant-output discrete tables: Parse
tableValues. If all values are equal (or within 0.02): the table is constant. Check the constant against erasure thresholds — near-0 for alpha (transparent), near-0 or near-1 for RGB channels (dark-out / white-out). - Detect 1-bin tables on alpha: A single-value
tableValuesonfeFuncA type="discrete"maps 100% of alpha values to that one value. If that value is 0: flag unconditionally as full transparency. Minimal footprint but maximum impact. - Detect high-frequency alternation in discrete tableValues: Compute the number of 0↔1 transitions in
tableValues. If transitions ≥ N/2 (where N = number of bins), the table is a high-frequency alpha quantizer. Flag as spatial-readability attack on consent elements. This catches SA-CSS-DIS-003 without requiring canvas simulation. - Detect JS-swappable tableValues on feFuncA: A single-bin
tableValues="1"onfeFuncA type="discrete"is trivially swappable to all-transparent. Flag single-bin alpha-maximum tables in consent ancestor filters as JS-override risks and deploy MutationObserver on thetableValuesattribute. - Contrast type=discrete and type=table detectors independently: The discrete bin formula differs from the table piecewise-linear formula. Detection logic for SA-CSS-TAB patterns does not automatically cover SA-CSS-DIS patterns. Both type=table and type=discrete branches of feComponentTransfer require separate constant-output and frequency checks.
SkillAudit findings for this attack surface
feFuncA type="discrete" tableValues="0" — single-bin nearest-neighbor map: all alpha values quantize to 0; element fully transparent; minimal attack: one feFuncA, one attribute, one value; structurally identical to brightness filters; constant-output single-bin check required.feFuncR/feFuncG/feFuncB type="discrete" tableValues="1 1 1 1 1" — 5 bins all mapping to 1; all RGB → 1; white-out; constant-output with N=5 evades N=1 single-bin detectors; constant-output check generalized to all-equal tableValues required.feFuncA type="discrete" tableValues="0 1 0 1 0 1 0 1 0 1" — 10 alternating bins; 1-bit alpha quantization; checkerboard transparency on anti-aliased text; average alpha ≈ 0.5 (evades mean-alpha threshold checks); high-frequency transition count detection (≥5 transitions in 10 bins) required.feFuncA tableValues "1"→"0" — initial single-bin all-opaque identity; click-time single-bin all-transparent erase; MutationObserver on feFuncA tableValues attribute in consent ancestor filters required.