← Blog

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.

By SkillAudit  |  23 Oct 2026

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

IDChanneltableValuesBinsEffectSeverity
SA-CSS-DIS-001A"0"1 all-zeroAll alpha → 0 → transparentCritical
SA-CSS-DIS-002R, G, B"1 1 1 1 1"5 all-oneAll RGB → 1 → white-outCritical
SA-CSS-DIS-003A"0 1 0 1 0 1 0 1 0 1"10 alternating1-bit alpha checkerboard → unreadableHigh
SA-CSS-DIS-004A (mousedown)"1" → "0"1 opaque→transparentFully opaque → transparent at click timeCritical

Defences

SkillAudit findings for this attack surface

CRITICAL SA-CSS-DIS-001: 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.
CRITICAL SA-CSS-DIS-002: 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.
HIGH SA-CSS-DIS-003: 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.
CRITICAL SA-CSS-DIS-004: JS mousedown changes 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.

Related: feComponentTransfer type=table attacks  |  feComponentTransfer type=gamma attacks  |  feComponentTransfer consent attacks overview  |  feColorMatrix consent attacks

← Blog  |  Security Checklist