← Blog

MCP server SVG color-interpolation-filters sRGB vs linearRGB consent security

Four SA-CSS-SRGB consent attack patterns exploiting the difference between color-interpolation-filters=sRGB and color-interpolation-filters=linearRGB — the SVG property that determines whether filter primitive computations (blur, blend, composite) operate in gamma-compressed sRGB space or in physically linear light space, producing different pixel outputs and enabling detector/browser simulation-gap attacks.

By SkillAudit  |  23 Oct 2026

The color-interpolation-filters CSS/SVG property controls the color space in which SVG filter primitive computations are performed. The two legal values are sRGB (gamma-compressed, perceptual) and linearRGB (physically linear, radiometrically correct). The SVG specification (§15.4) sets the default to linearRGB for filter effects, meaning that feGaussianBlur, feBlend, feComposite, and all other filter primitives operate in linear light space unless the attribute overrides this.

The key difference: sRGB values are nonlinearly related to linear light intensities via the sRGB gamma curve (approximately C_linear ≈ C_sRGB^2.2). A dark pixel with sRGB value 0.1 has a linear light value of approximately 0.01. This 10× difference in linear light energy means that blur, blend, and composite operations produce noticeably different outputs in the two color spaces. Blurs appear brighter in linearRGB than in sRGB for the same pixel values, because linear averaging of light preserves more energy than averaging compressed values.

Core attack mechanism — simulation gap: Detection tools that simulate filter output to measure consent text contrast often default to linearRGB (following the SVG spec default). A filter that explicitly declares color-interpolation-filters="sRGB" will be rendered in sRGB by the browser but simulated in linearRGB by the detector. The detector measures a different (typically higher) contrast than the browser actually shows. The attacker exploits this gap: the filter is designed so that the detector's linearRGB simulation reports passing contrast, while the browser's sRGB rendering produces failing contrast.

SA-CSS-SRGB-001: feGaussianBlur in sRGB mode (darker blur output for dark text)

A feGaussianBlur applied to dark text on a white background produces a blurred result where the text pixels spread into the surrounding white region. In linearRGB mode, the Gaussian weights average linear light values — the spreading dark text pixels have linear values ≈ 0.01 and the surrounding white pixels have linear value 1.0. The blur output is dominated by the bright white surroundings: the blurred text region is lighter (higher averaged light energy).

In sRGB mode, the same blur operates on sRGB-compressed values. The dark pixels have sRGB value 0.1 and white pixels have sRGB value 1.0. The sRGB average of 0.1 and 1.0 is 0.55 — a mid-gray. After converting back to display, this corresponds to a darker blur than the linearRGB equivalent (linearRGB average of 0.01 and 1.0 = 0.505 linear → sRGB ≈ 0.74, lighter).

<filter id="srgb1" color-interpolation-filters="sRGB">
  <feGaussianBlur in="SourceGraphic" stdDeviation="2"/>
</filter>
<!-- Blur in sRGB: darker output for dark text on white bg than linearRGB would produce -->

Consent text blurred in sRGB mode appears darker at the blur edges than a detector simulating in linearRGB would predict. This is a readability attack via contrast elevation in the wrong direction: the blur spreads less energy (darker), making blurred-edge text actually harder to read while the detector measures higher-than-actual contrast.

SA-CSS-SRGB-002: feBlend multiply in linearRGB mode (lighter multiply output)

The feBlend multiply mode computes out = in1 × in2 per channel. In sRGB mode, the multiply is performed on gamma-compressed values. In linearRGB mode, the multiply is performed on linear light values.

For a gray consent text panel (foreground sRGB ≈ 0.5, linear ≈ 0.214) blended via multiply with a gray background (sRGB ≈ 0.7, linear ≈ 0.447):

In this specific case the outputs are similar, but for saturated or high-contrast inputs the modes diverge significantly. The attacker exploits this by constructing a filter where the linearRGB multiply produces higher output (lighter, lower contrast with dark text foreground) than the sRGB equivalent. The detector simulating in sRGB sees adequate contrast; the browser rendering in linearRGB shows lower contrast.

<filter id="srgb2" color-interpolation-filters="linearRGB">
  <feBlend in="SourceGraphic" in2="SourceGraphic" mode="multiply"/>
</filter>
<!-- linearRGB multiply on specific input values → lighter output than sRGB would produce -->

SA-CSS-SRGB-003: JS mousedown color-interpolation-filters attribute swap

Initial state: color-interpolation-filters="linearRGB" (explicit, matching the default). The filter is designed so that in linearRGB mode it appears to render consent text with adequate contrast. On mousedown, the script changes the attribute to "sRGB". All subsequent filter primitive computations revert to sRGB space. For the specific filter chain, sRGB mode produces lower contrast on the consent text — below WCAG 4.5:1. The element is still rendered; the contrast has simply dropped at click time.

consentElement.addEventListener("mousedown", () => {
  const filter = document.getElementById("consentFilter");
  filter.setAttribute("color-interpolation-filters", "sRGB");
  // filter re-executes in sRGB space; consent text contrast drops at click time
});

MutationObserver on the color-interpolation-filters attribute of filter elements in consent ancestor filters is required to detect this runtime change.

SA-CSS-SRGB-004: detector/browser mode divergence (the core evasion)

This is not a standalone attack pattern but the underlying evasion mechanism that makes SA-CSS-SRGB-001/002 attacks effective against static analyzers. The attack chain is:

  1. Attacker builds a filter that, in sRGB mode, reduces consent text WCAG contrast below 4.5:1
  2. Attacker does not set color-interpolation-filters, leaving it unset (browsers will use the filter element's inherited value, which defaults to linearRGB per SVG spec — but some browsers implement this differently)
  3. Alternatively, attacker explicitly sets color-interpolation-filters="sRGB" to guarantee sRGB rendering
  4. The detection tool, simulating in linearRGB, measures a different (higher) contrast than the browser renders
  5. The tool reports passing; the browser renders failing
<!-- Explicit sRGB declaration: browser uses sRGB; detector may default to linearRGB -->
<filter id="srgb4" color-interpolation-filters="sRGB">
  <feGaussianBlur stdDeviation="1.5"/>
  <feBlend mode="multiply" in2="SourceGraphic"/>
</filter>
<!-- Detector (linearRGB): reports contrast C1; Browser (sRGB): renders contrast C2 < C1 -->

The defence is simple but implementation-specific: detection tools must read and honor the color-interpolation-filters attribute on every filter element, and simulate in the declared color space rather than assuming linearRGB.

Attack summary

IDProperty valuePrimitiveEffectSeverity
SA-CSS-SRGB-001sRGBfeGaussianBlurDarker blur spread; detector overestimates contrastMedium
SA-CSS-SRGB-002linearRGBfeBlend multiplyLighter multiply output; input-dependent contrast shiftMedium
SA-CSS-SRGB-003linearRGB→sRGB mousedownanyClick-time color-space switch; contrast dropsHigh
SA-CSS-SRGB-004sRGB (explicit)blur+blend chainDetector/browser simulation gap; evasion patternHigh

Defences

SkillAudit findings for this attack surface

MEDIUM SA-CSS-SRGB-001: color-interpolation-filters="sRGB" on filter with feGaussianBlur — sRGB-mode blur produces darker spread for dark text than linearRGB mode; detector simulating in linearRGB overestimates post-blur contrast; contrast gap depends on specific text colors and blur sigma; detection: simulate in declared color space.
MEDIUM SA-CSS-SRGB-002: color-interpolation-filters="linearRGB" on filter with feBlend mode=multiply — linearRGB multiply output differs from sRGB for non-gray inputs; specific color combinations produce lower contrast in linearRGB than sRGB; attacker targets specific consent text/background color pairs; detection: evaluate multiply formula in declared mode.
HIGH SA-CSS-SRGB-003: JS mousedown sets filter color-interpolation-filters from linearRGB to sRGB — initial state: filter safe in linearRGB; click-time: sRGB mode changes all intermediate values; consent text contrast drops; MutationObserver on color-interpolation-filters attribute required.
HIGH SA-CSS-SRGB-004: explicit color-interpolation-filters="sRGB" on blur+blend chain — detector defaults to linearRGB simulation; browser renders in sRGB; contrast C_detector > C_browser; detector reports passing; browser renders failing; the core simulation-gap evasion for color-space attacks; all detection tools must honor the declared color space.

Related: color-interpolation consent attacks overview  |  feBlend mode=luminosity consent attacks  |  feColorMatrix type=saturate consent attacks

← Blog  |  Security Checklist