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.
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):
- sRGB multiply: 0.5 × 0.7 = 0.35 (sRGB output)
- linearRGB multiply: 0.214 × 0.447 = 0.096 (linear), converted back to sRGB ≈ 0.35
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:
- Attacker builds a filter that, in sRGB mode, reduces consent text WCAG contrast below 4.5:1
- 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) - Alternatively, attacker explicitly sets
color-interpolation-filters="sRGB"to guarantee sRGB rendering - The detection tool, simulating in linearRGB, measures a different (higher) contrast than the browser renders
- 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
| ID | Property value | Primitive | Effect | Severity |
|---|---|---|---|---|
| SA-CSS-SRGB-001 | sRGB | feGaussianBlur | Darker blur spread; detector overestimates contrast | Medium |
| SA-CSS-SRGB-002 | linearRGB | feBlend multiply | Lighter multiply output; input-dependent contrast shift | Medium |
| SA-CSS-SRGB-003 | linearRGB→sRGB mousedown | any | Click-time color-space switch; contrast drops | High |
| SA-CSS-SRGB-004 | sRGB (explicit) | blur+blend chain | Detector/browser simulation gap; evasion pattern | High |
Defences
- Honor color-interpolation-filters in detection tools: Every detection simulation must read the
color-interpolation-filtersattribute on each filter element and simulate in the declared color space. If the attribute is absent, use linearRGB per SVG spec default. Do not hardcode linearRGB as the simulation mode. - Apply gamma correction when measuring contrast in filtered output: After rendering a filtered element to an offscreen canvas, ensure that the canvas image data is interpreted in the correct color space. Browser canvas APIs return sRGB values; WCAG contrast calculations must linearize them correctly before computing relative luminance.
- MutationObserver on filter color-interpolation-filters: Runtime changes to the
color-interpolation-filtersattribute on filter elements in consent ancestor filters trigger SA-CSS-SRGB-003. Monitor the attribute directly. - Flag unexpected explicit color-interpolation-filters declarations: Legitimate filter usage rarely requires an explicit color-interpolation-filters declaration (the SVG default is correct for most uses). An explicit
color-interpolation-filters="sRGB"on a filter applied to a consent element should be flagged for manual review, as it may indicate an attempt to exploit the detector/browser simulation gap.
SkillAudit findings for this attack surface
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.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.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.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.