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

High CSS oklab white-midpoint gradient attack: MCP server sets 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.
High CSS hsl longer-hue white island attack: MCP server uses 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.
High CSS oklch achromatic grey wash attack: MCP server uses 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.
Medium CSS display-p3 gamut-clamp cross-browser mismatch: MCP server uses 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.

← Blog  |  Security Checklist