MCP server CSS color-interpolation-method security: oklab white midpoint collision, hsl hue-longer white island, oklch chroma-zero grey wash, and sRGB negative-value clamp attacks on gradient backgrounds
Published 2026-07-23 — SkillAudit Research
CSS Color Level 4 introduced the color-interpolation-method keyword for gradient functions (linear-gradient, radial-gradient, conic-gradient, and their repeating variants). This keyword controls which color space is used to interpolate between gradient stop colors. The syntax is linear-gradient(in <color-space> <hue-interpolation-method>?, stop1, stop2...).
Different color spaces interpolate between the same two stop colors differently. For MCP server attacks, the critical property is this: two stop colors that look visually distinct can interpolate through a third color — including white or the page background color — in a specific color space. An MCP server that controls a gradient background can create a region of white-on-white or same-color-as-background for the exact pixel rows where the disclosure text renders, without the gradient stop colors themselves being white or suspicious.
Supported browsers: color-interpolation-method is supported in Chrome 111+, Safari 16.2+, and Firefox 113+. On older browsers, gradient stops interpolate in sRGB by default. MCP servers can use @supports (background: linear-gradient(in oklab, red, blue)) to gate the attack to browsers where it is effective.
Attack 1: in oklab white midpoint — complement colors produce near-white midpoint
In the oklab color space, complementary colors (perceptually opposite colors) have opposite chroma vectors. Interpolating between them in oklab causes the chroma to pass through zero at the midpoint — and zero chroma in oklab is achromatic (grey or white, depending on the L value). Two colors with high L (lightness) values and opposite hues will produce a near-white or pure-white midpoint in oklab interpolation:
/* Attack 1: two complementary pastel colors produce a white midpoint in oklab */
/* In sRGB interpolation, yellow and blue interpolate through grey-green — not white. */
/* In oklab interpolation, pastel yellow + pastel blue produce a near-white midpoint. */
/* MCP server sets the gradient background on the consent disclosure wrapper: */
.consent-wrapper {
background: linear-gradient(
in oklab,
oklch(95% 0.08 90deg), /* pastel yellow — stop at 0% */
oklch(95% 0.08 270deg) /* pastel blue — stop at 100% */
);
/* In oklab interpolation:
At 50% (midpoint): chroma lerp = 0.08 + (0.08 * -1) = 0 (opposite hue vectors cancel)
But L also interpolates: L = 95% at both stops → L = 95% at midpoint.
oklch(95% 0 any-hue) = pure white (achromatic white at L=95%).
The midpoint is white. */
}
/* Consent disclosure text is positioned at the 50% midpoint zone: */
.consent-disclosure {
position: absolute;
top: 50%; /* text renders on the white midpoint of the gradient background */
color: white; /* or light grey — matches the white gradient midpoint */
/* White text on a white midpoint background = invisible */
}
/* The gradient stops are not white — they are pastel yellow and pastel blue.
A scanner looking for white background or white-on-white patterns does not flag this.
The hostile white region emerges from the interpolation mathematics. */
// Detection: sample background color at the text rendering location
function detectGradientWhiteMidpoint(el) {
const rect = el.getBoundingClientRect();
const canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
const ctx = canvas.getContext('2d');
// drawImage at the element's center to sample background color
// Note: this requires a rendered canvas context with the page visible
// In practice, use getComputedStyle to check the background property
const cs = window.getComputedStyle(el);
const bg = cs.background || cs.backgroundColor;
// Flag gradients using oklab or oklch interpolation method
if (bg.includes('in oklab') || bg.includes('in oklch') || bg.includes('in hsl')) {
console.warn('SECURITY: consent element has color-interpolation-method gradient background — manual review required', {
element: el,
background: bg.substring(0, 100) // truncate for logging
});
return true;
}
return false;
}
Attack 2: in hsl hue-longer — large hue difference creates white-passing island
HSL hue interpolation has two modes: shorter hue (default — takes the shorter arc around the hue wheel) and longer hue (takes the longer arc). For two colors with hues 30° apart, the longer arc traverses 330° of the hue wheel. Passing through 60° → 120° → 180° → 240° → 300° → 360° → 30°, the interpolation passes through cyan, blue, magenta, and red. At certain L/S combinations, some of these intermediate hues produce near-white or near-background-colored regions:
/* Attack 2: hsl longer hue arc passes through white-passing colors */
/* In hsl, the lightness dimension has extremes at white (L=100%) and black (L=0%).
The hue does not independently create white — but combined with high lightness
and low saturation, rotating through the "grayed" regions of the hsl wheel
can produce colors that match a white or near-white background. */
.consent-wrapper {
background: linear-gradient(
in hsl longer hue,
hsl(30 80% 92%), /* near-white peach — stop at 0% */
hsl(60 80% 92%) /* near-white yellow — stop at 100% */
);
/* With 'longer hue', the interpolation takes the 300°-long arc:
30° → 360°/0° → 300° → 240° → 180° → 120° → 60°.
At 180° (halfway through the arc), color is hsl(180deg, 80%, 92%) = near-white cyan.
If the background page is white, this near-white cyan (ΔE ≈ 3) may not be flagged
by a basic contrast checker.
If text color is set to the same near-white: invisible. */
}
/* More direct attack: use hsl longer hue between colors that cross the white axis: */
.consent-wrapper {
background: linear-gradient(
in hsl longer hue,
hsl(120deg 100% 95%), /* near-white green */
hsl(240deg 100% 95%) /* near-white blue */
);
/* Longer arc: 120° → (going backwards) 60° → 0° → 300° → 240°.
At 0° (red, ~1/3 of arc): hsl(0deg, 100%, 95%) = very light red/white.
All stops have L=95%, so all interpolated values are very light = near-white.
Text with color matching the background = invisible across the whole gradient. */
}
// Detection: flag hsl longer hue gradients on consent elements
function detectHslLongerHueGradient(el) {
const cs = window.getComputedStyle(el);
const bg = cs.backgroundImage || '';
if (bg.includes('in hsl longer') || bg.includes('in hsl hue-longer')) {
const textColor = cs.color;
console.error('SECURITY: consent element uses hsl longer hue gradient background — may create white-passing region', {
element: el,
backgroundImage: bg.substring(0, 120),
textColor
});
return true;
}
return false;
}
Attack 3: in oklch — chroma-zero grey wash at 50% midpoint
oklch (cylindrical lightness-chroma-hue) interpolation has the same chroma-cancellation property as oklab. When two colors have opposite hues (180° apart) and identical L and C values, interpolation in oklch drives chroma through zero at the 50% midpoint, producing an achromatic grey. The L value at the midpoint determines whether this is white, light grey, mid grey, or dark grey:
/* Attack 3: oklch chroma-zero grey wash at gradient midpoint */
/* Two colors 180° apart in hue, same L and C: */
.consent-wrapper {
background: linear-gradient(
in oklch,
oklch(85% 0.15 40deg), /* warm brown-orange */
oklch(85% 0.15 220deg) /* cool blue — exactly 180° offset */
);
/* In oklch: at 50% midpoint:
L interpolates: 85% → 85% = 85% (constant)
C interpolates: 0.15 → 0.15 = 0.15 (constant magnitude)
Hue interpolates: 40° → 220° (shorter arc = 180°, midpoint = 130° = teal-green)
Wait — with shorter hue (default): midpoint hue is 130° (teal) — NOT grey.
For grey/white midpoint, we need shorter hue where the vectors cancel:
The chroma VECTOR cancels in oklch/oklab only with specific configurations. */
}
/* Correct attack — using two colors on the same hue but different chroma/lightness: */
/* Actually the oklab vector cancellation requires opposite lab components (a*, b*).
For pure white: we need L→1, a→0, b→0 in oklab.
Two complementary colors: oklch(90% 0.1 45°) and oklch(90% 0.1 225°).
In oklab: they have opposite a* and b* values.
Midpoint: a*=0, b*=0, L=90% → achromatic at L=90% ≈ very light grey/white. */
.consent-wrapper {
background: linear-gradient(
in oklab,
oklch(90% 0.1 45deg), /* warm light tan */
oklch(90% 0.1 225deg) /* cool light blue — opposite oklab vectors */
);
/* Midpoint: oklab(90% 0 0) = near-white achromatic.
If consent text color is white or near-white: invisible at midpoint. */
}
/* The page background is white (#fff = oklab(100% 0 0)).
The gradient midpoint is oklab(90% 0 0) — very light grey, ΔE ≈ 10 from pure white.
A WCAG 1.5:1 contrast checker might pass this (light grey on white has 1.3:1 ratio).
With white text (1:1 contrast on white page), the text is effectively invisible
in the midpoint zone of the gradient. */
// Detection: check text contrast against the gradient background in the text region
function detectOklabGreyMidpoint(el) {
const cs = window.getComputedStyle(el);
const bg = cs.backgroundImage || '';
if (bg.includes('in oklab') || bg.includes('in oklch')) {
const color = cs.color;
// Check if text color is light (near-white)
const colorMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
if (colorMatch) {
const [, r, g, b] = colorMatch.map(Number);
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
if (luminance > 0.85) { // near-white text on oklab gradient
console.error('SECURITY: consent element has near-white text color with oklab gradient background', {
element: el,
textColor: color,
textLuminance: luminance,
backgroundImage: bg.substring(0, 120)
});
return true;
}
}
}
return false;
}
Attack 4: in srgb — negative interpolation clamp creates unexpected colors
When CSS gradients interpolate in sRGB between colors that produce intermediate values outside the [0, 1] range (out-of-gamut during interpolation), the browser clamps the values to the valid range. An MCP server can craft two stop colors such that the midpoint's intermediate unclamped values would be (say) -0.1, 1.1, -0.1 — which after clamping become 0, 1, 0 = green. If the page background is green and the text is black on a green background, contrast passes, but if the "consent disclosure" text is being rendered on a green background where the user expects white, it creates a visual disconnect:
/* Attack 4: sRGB out-of-gamut clamping creates unexpected midpoint colors */
/* More practically, this attack uses display-p3 to sRGB conversion clamping: */
.consent-wrapper {
background: linear-gradient(
in display-p3,
color(display-p3 1.2 -0.1 -0.1), /* out-of-sRGB-gamut red-ish */
color(display-p3 -0.1 -0.1 1.2) /* out-of-sRGB-gamut blue-ish */
);
/* On sRGB displays (most monitors), display-p3 values are gamut-mapped.
The midpoint in display-p3 space is color(display-p3 0.55 -0.1 0.55) = out-of-sRGB purple.
After gamut mapping to sRGB: some browsers clip to nearest sRGB color, others compress.
The rendered midpoint color depends on the browser's gamut mapping strategy —
potentially producing a color that matches the page background exactly on some browsers
while appearing distinctly on others. This creates a cross-browser attack: the disclosure
is hidden on one browser but visible on another. Security auditors using different browsers
get different results. */
}
/* Simpler sRGB negative clamp: */
/* Note: in standard CSS gradient in sRGB, values are already in [0,1] for valid colors.
But when interpolating in display-p3 and converting at render time, out-of-gamut
intermediates can produce unexpected results in the clamped sRGB output. */
// Detection: any display-p3 or rec2020 gradient on a consent disclosure is suspicious
function detectOutOfGamutGradient(el) {
const cs = window.getComputedStyle(el);
const bg = cs.backgroundImage || '';
if (bg.includes('display-p3') || bg.includes('rec2020') || bg.includes('a98-rgb')) {
console.warn('SECURITY: consent element uses wide-gamut color space gradient — manual contrast review needed', {
element: el,
backgroundImage: bg.substring(0, 120)
});
return true;
}
return false;
}
Contrast checkers may miss interpolation attacks: WCAG contrast checking tools compute contrast between the declared text color and the declared background color. For gradient backgrounds, they typically sample at the gradient stops. The hostile region is the interpolated midpoint — not at a declared stop color. A WCAG contrast check that passes all stop colors individually may still have a consent-text region at the midpoint where contrast is below 4.5:1 or where the background is nearly identical to the text color.
Attack summary
| Attack | Color interpolation method | Effect | Detection approach | Severity |
|---|---|---|---|---|
| oklab white midpoint | in oklab, complementary L=90% colors |
Near-white at 50% midpoint; white text invisible | Flag in oklab gradients with light text color |
High |
| hsl hue-longer white island | in hsl longer hue, near-white high-L stops |
Near-white region at hue arc midpoint | Flag in hsl longer gradients on consent elements |
High |
| oklch chroma-zero grey wash | in oklab / oklch, opposite hue vectors |
Achromatic grey at midpoint, matches near-white background | Flag oklab/oklch gradients + near-white text color | High |
| display-p3 gamut-clamp mismatch | in display-p3, out-of-sRGB stop values |
Browser-dependent midpoint color; hidden on some browsers | Flag wide-gamut gradient on consent elements | Medium |
Consolidated finding blocks
background: linear-gradient(in oklab, color1, color2) with complementary colors at L≥85%, creating a near-white midpoint region. Consent text with near-white color is invisible at the midpoint zone. Detection: flag in oklab/in oklch gradients on consent elements with light text colors.
in hsl longer hue interpolation between near-white high-lightness colors. The 300° arc crosses through multiple hue regions, creating near-white regions at hue positions where lightness compounds with saturation to match the background. Detection: flag in hsl longer hue gradients on consent elements.
in oklab gradient between colors whose oklab a*/b* vectors cancel at the midpoint, producing achromatic near-white. Text color is set to match. Detection: compute WCAG contrast between text color and gradient midpoint color using the CIE ΔE formula, not just declared stop colors.
in display-p3 gradient with out-of-sRGB-gamut stop values. The rendered midpoint color varies by browser gamut mapping strategy — invisible on some browsers, visible on others. Security auditors may report the disclosure as visible while users on other browsers see it hidden. Detection: flag any wide-gamut color-space gradient on consent elements for manual multi-browser review.