MCP server SVG feComponentTransfer table consent security
Four SA-CSS-TAB consent attack patterns using feComponentTransfer type="table" — piecewise-linear interpolation between tableValues breakpoints exploited to erase alpha, produce white-out, and generate spatial dithering that destroys consent text readability without triggering zero-output or transparency checkers.
The feComponentTransfer SVG filter primitive applies a per-channel transfer function to pixel values. When type="table", the function performs piecewise-linear interpolation between breakpoints specified in the tableValues attribute (SVG §15.10). For N values, the input domain [0, 1] is divided into N−1 equal segments of width 1/(N−1). An input value in segment k is linearly interpolated between tableValues[k] and tableValues[k+1]. This flexible lookup-table mechanism can express arbitrary monotonic or non-monotonic transfers — including constant-output functions and high-frequency oscillating patterns — making it a versatile erasure tool.
Constant-output via identical breakpoints: A table with all breakpoints equal to the same value is a constant-output transfer function. tableValues="0 0" on feFuncA maps every alpha value to 0 (transparent). tableValues="1 1" on feFuncR feFuncG feFuncB maps every RGB value to 1 (white). No input-dependent variation — the transfer is identically constant, indistinguishable from a direct channel set but expressed as a table lookup that evades scalar checks.
SA-CSS-TAB-001: feFuncA tableValues="0 0" (alpha erasure)
The two-element table tableValues="0 0" defines a single segment [0, 1] that linearly interpolates between 0 and 0 — that is, outputs exactly 0 for all inputs. Applied to the alpha channel via feFuncA, this maps every pixel's alpha to 0. The element becomes fully transparent.
This differs from SA-CSS-GAM-002 (gamma type, amplitude=0) in its encoding: instead of amplitude="0", the attack uses a table with two identical zero entries. Tools that check for amplitude="0" will not flag this. The semantic effect is identical but the syntactic representation is distinct, requiring separate detection logic.
<filter id="tab1">
<feComponentTransfer>
<feFuncA type="table" tableValues="0 0"/>
</feComponentTransfer>
</filter>
<!-- N=2: segment [0,1] interpolates between 0 and 0 = constant 0; all alpha → 0; transparent -->
SA-CSS-TAB-002: feFuncR/feFuncG/feFuncB tableValues="1 1" (white-out)
The same constant-table technique applied to RGB channels: tableValues="1 1" maps every channel value to 1. All RGB channels become 1, producing pure white for every pixel. The element is a white rectangle — invisible against white/light backgrounds.
Both tableValues entries (1 and 1) are within the legal SVG range [0, 1]. An attribute-range validator will not flag either value. The attack is only detectable by evaluating whether the table is constant (all breakpoints equal or convergent) and what constant it maps to.
<filter id="tab2">
<feComponentTransfer>
<feFuncR type="table" tableValues="1 1"/>
<feFuncG type="table" tableValues="1 1"/>
<feFuncB type="table" tableValues="1 1"/>
</feComponentTransfer>
</filter>
<!-- N=2: segment [0,1] → constant 1; all RGB channels → 1.0; white-out -->
SA-CSS-TAB-003: feFuncR tableValues high-frequency alternation (dithering)
A 10-value table on the R channel alternates between 0 and 1 across 9 segments. Input in segment 0 ([0, 1/9]) interpolates 0→1; segment 1 ([1/9, 2/9]) interpolates 1→0; segment 2 ([2/9, 3/9]) interpolates 0→1; and so on. The resulting transfer function is a high-frequency sawtooth on the R channel.
For a dark text pixel with R ≈ 0.2 (dark gray), segment index = floor(0.2 × 9) = 1 (interpolating 1→0): output ≈ 0.2. For an adjacent anti-aliased pixel at R ≈ 0.15: segment 1 (interpolating 1→0): ≈ 0.35. Adjacent pixels with similar R values produce substantially different outputs, creating spatial high-frequency noise. The R channel alternation is visually disruptive for any text that relies on consistent foreground color across its glyph extent.
<filter id="tab3">
<feComponentTransfer>
<feFuncR type="table" tableValues="0 1 0 1 0 1 0 1 0 1"/>
</feComponentTransfer>
</filter>
<!-- 9 segments alternating 0→1→0→1...; R channel dithering destroys text anti-aliasing -->
Dithering evasion: SA-CSS-TAB-003 does not set any channel to zero and does not erase alpha. WCAG contrast measurements that sample single pixels may find locally passing ratios. Detection requires evaluating the transfer function for spatial frequency: a constant-or-near-constant table passes; a high-oscillation table on any channel in a consent ancestor filter is a readability attack.
SA-CSS-TAB-004: JS mousedown replaces feFuncA tableValues "0 1" → "0 0" (click-time erase)
The feComponentTransfer starts with feFuncA type="table" tableValues="0 1". This is the identity transfer for the alpha channel: a two-segment table from 0 to 1 maps input alpha linearly to itself (alpha=0 → 0, alpha=0.5 → 0.5, alpha=1 → 1). The element renders normally. On mousedown, the script replaces tableValues with "0 0" — the constant-zero table from SA-CSS-TAB-001. All alpha values immediately map to 0 and the element disappears.
consentElement.addEventListener("mousedown", () => {
const fa = document.getElementById("consentFilter")
.querySelector("feFuncA[type='table']");
fa.setAttribute("tableValues", "0 0"); // collapse identity to constant-zero
// element immediately transparent; click proceeds on now-invisible element
});
Attack summary
| ID | Channel | tableValues | Segments | Effect | Severity |
|---|---|---|---|---|---|
| SA-CSS-TAB-001 | A | "0 0" | 1 constant-0 | Full alpha erasure → transparent | Critical |
| SA-CSS-TAB-002 | R, G, B | "1 1" | 1 constant-1 | All RGB → 1 → white-out | Critical |
| SA-CSS-TAB-003 | R | "0 1 0 1 0 1 0 1 0 1" | 9 oscillating | High-freq R dithering → unreadable | High |
| SA-CSS-TAB-004 | A (mousedown) | "0 1" → "0 0" | 1 identity→0 | Identity → transparent at click time | Critical |
Defences
- Detect constant-output tables: For each
feFuncX type="table"in a consent ancestor filter, parsetableValuesas a space-separated list of floats. If all values are within 0.02 of each other (near-constant), the table is effectively constant — check the constant value against the erasure thresholds (near-0 for alpha, near-0 or near-1 for RGB channels). - Evaluate whether the identity table is the initial state for JS override:
feFuncA tableValues="0 1"is an exact identity. Flag this pattern in consent ancestor filters as a JS-override risk, especially if the element has attached mousedown event listeners. - Detect high-frequency oscillation: For any
feFuncX type="table"with N > 5 values, compute the number of sign changes in the finite differences (tableValues[k+1] − tableValues[k]). If ≥3 sign changes in N−1 segments, flag as spatial-frequency attack on consent readability. - MutationObserver on feFuncA and feFuncX tableValues: Runtime changes to
tableValuesonfeFuncAwithin consent ancestor filters are the primary SA-CSS-TAB-004 vector. Monitor thetableValuesattribute directly.
SkillAudit findings for this attack surface
feFuncA type="table" tableValues="0 0" — two-element constant-zero table; all alpha values map to 0 via piecewise-linear interpolation; element fully transparent; distinct from amplitude=0 (gamma) encoding; requires table-specific constant-output detection.feFuncR/feFuncG/feFuncB type="table" tableValues="1 1" — two-element constant-one table; all RGB channels map to 1; white-out; all tableValues in legal [0,1] range; passes attribute-range validators; constant-output table detection required.feFuncR type="table" tableValues="0 1 0 1 0 1 0 1 0 1" — 9 alternating segments; high-frequency R-channel dithering; adjacent pixels with similar input R values produce different outputs; text anti-aliasing destroyed; no channel set to zero; spatial-frequency analysis of tableValues required for detection.feFuncA tableValues from "0 1" (identity) to "0 0" (constant-zero) — initial state exact alpha identity; click-time alpha erasure; MutationObserver on feFuncA tableValues attribute in consent ancestor filters required.