← Blog

MCP server CSS color format obfuscation consent security

CSS Color Level 4 introduced multiple new color function syntaxes including hsl(), oklch(), lab(), and color(). Each can express the color white — or background-matching colors — in syntax that looks like a legitimate color specification but which audit tools pattern-matching for white, #fff, #ffffff, or rgb(255,255,255) will miss entirely. The defense is computed style color comparison rather than stylesheet text pattern matching. Four SA-CSS-HSL patterns document the primary color format obfuscation attack vectors.

By SkillAudit  |  30 Oct 2026

Color format obfuscation attacks target the gap between what an audit tool recognizes as “white” and what the browser actually renders as white. The legacy audit approach is pattern-matching: find color: white, color: #fff, or color: rgba(255,255,255,...) in the stylesheet and flag as suspicious when the page background is white. The modern CSS color system provides multiple alternative syntaxes that all resolve to identical white rendering but do not match legacy pattern sets.

The correct audit approach evaluates the rendered color: getComputedStyle(el).color returns a normalized rgb() or rgba() string in all modern browsers regardless of the original color function syntax. A check of getComputedStyle(consentTextEl).color === 'rgb(255, 255, 255)' catches all white-color attacks regardless of the source syntax. The vulnerability lies in static text analysis tools that never evaluate computed values.

SA-CSS-HSL-001: color: hsl(0, 0%, 100%) — white in HSL syntax

HSL (Hue, Saturation, Lightness) is a widely-used CSS color format. hsl(0, 0%, 100%) specifies hue=0° (red axis), saturation=0% (achromatic — no color), lightness=100% (maximum brightness). The result is pure white. The value appears to be a reddish or hue-based color at first glance — hsl(0, 0%, 100%) looks like a low-saturation red to a human reader who doesn't evaluate the numbers. The zero saturation means hue is irrelevant, and 100% lightness is white in HSL.

/* Attack: white expressed as HSL — defeats pattern matching for 'white', '#fff' */
.consent-text {
  color: hsl(0, 0%, 100%);
  /* hsl(hue=0°, saturation=0%, lightness=100%) = pure white
     → text is white-on-white background (invisible)

     Visual deception: reader sees "hsl(0, 0%, 100%)" and may not immediately
     recognize this as white — the hue value 0 suggests red family

     Audit tool bypass: grep/search for 'color: white' → no match
                       grep for '#fff' → no match
                       grep for 'rgb(255,255,255)' → no match */
}

/* Variations: other HSL whites: */
.consent-text { color: hsl(360, 0%, 100%); }  /* hue=360° = same as 0° */
.consent-text { color: hsl(180, 0%, 100%); }  /* any hue, 0% sat, 100% light = white */
.consent-text { color: hsla(0, 0%, 100%, 1); } /* with explicit full alpha */

/* Background-matching non-white: */
.consent-text { color: hsl(220, 30%, 95%); }
/* If page background is also hsl(220, 30%, 95%) — same computed color = invisible */

/* Correct detection:
   getComputedStyle(consentTextEl).color
   → Browser normalizes all color formats to rgb() or rgba():
   → 'rgb(255, 255, 255)' for any white

   Check: is computed text color identical or near-identical to computed background color?
   const textColor = getComputedStyle(consentTextEl).color;
   const bgColor = getComputedStyle(consentTextEl.offsetParent).backgroundColor;
   if (textColor === bgColor) → CRITICAL (text matches background) */

Any hue at 0% saturation and 100% lightness is white: The HSL model makes the hue parameter irrelevant at extreme saturation (0%) or lightness (0% or 100%). Attackers exploit the visual complexity of the HSL string — hsl(145, 0%, 100%) superficially suggests a greenish color — to deceive human reviewers while the computed result is always white.

CRITICAL
SA-CSS-HSL-001 — color: hsl(0, 0%, 100%): white in HSL syntax; defeats grep for '#fff'/'white'; browser normalizes to rgb(255,255,255). Detection: compare getComputedStyle.color to background color; flag matches → CRITICAL.

SA-CSS-HSL-002: color: oklch(1 0 0) — white in OKLCH perceptual color space

OKLCH (Oklab Lightness Chroma Hue) is a perceptual color space introduced in CSS Color Level 4. oklch(1 0 0) specifies lightness=1 (maximum, white), chroma=0 (achromatic), hue=0 (irrelevant at chroma=0). This is white in OKLCH. The syntax is extremely compact and unfamiliar: a three-number form with no percentage signs or function names that hint at color. Audit tools focused on legacy CSS color syntaxes typically have no pattern entry for OKLCH.

/* Attack: white in OKLCH — maximum obfuscation for newer audit tools */
.consent-text {
  color: oklch(1 0 0);
  /* oklch(lightness chroma hue):
     lightness = 1 = white (0 = black, 0.5 = mid-gray)
     chroma    = 0 = achromatic (no color saturation)
     hue       = 0 = irrelevant (achromatic when chroma=0)
     → pure white */
}

/* Variations: */
.consent-text { color: oklch(100% 0 0); }   /* lightness as percentage */
.consent-text { color: oklch(1 0 none); }    /* hue omitted via 'none' keyword */
.consent-text { color: oklch(1 0 360); }     /* any hue value at chroma=0 */

/* Near-white (hard to distinguish from white on white background): */
.consent-text { color: oklch(0.99 0.001 0); }
/* Lightness 0.99 is effectively white; chroma 0.001 introduces imperceptible color */

/* Browser support note:
   OKLCH is supported in Chrome 111+, Firefox 113+, Safari 15.4+
   All modern browsers used for Claude Desktop support OKLCH

   Detection: same as SA-CSS-HSL-001
   getComputedStyle(consentTextEl).color
   → normalizes OKLCH to 'rgb(255, 255, 255)' even though input was oklch()
   → background-color comparison catches this regardless of OKLCH syntax */

OKLCH audit tool coverage is lower than legacy formats: Audit tools built before CSS Color Level 4 proliferation (pre-2023) have no pattern entries for oklch(), oklab(), or other new color functions. The compact space-separated syntax — oklch(1 0 0) vs hsl(0, 0%, 100%) — provides higher obfuscation value even against updated tools.

CRITICAL
SA-CSS-HSL-002 — color: oklch(1 0 0): white in OKLCH perceptual color space; new CSS Level 4 syntax; low audit tool coverage. Detection: getComputedStyle.color normalizes to rgb(255,255,255); background comparison catches this → CRITICAL.

SA-CSS-HSL-003: color: lab(100 0 0) — white in CIE Lab color space

CIE Lab is a perceptual color space where L=0 is black and L=100 is white. lab(100 0 0) specifies L=100 (white), a=0 (no green-red shift), b=0 (no blue-yellow shift). CIE Lab is even less familiar to audit tools than OKLCH: it is a scientific color model with a decades-long history in color science but only recently added to CSS. An attacker using lab(100 0 0) benefits from the maximum audit coverage gap across legacy and most modern tools.

/* Attack: white in CIE Lab — maximum audit gap */
.consent-text {
  color: lab(100 0 0);
  /* lab(L a b):
     L = 100 = white (0 = black, 50 = mid-gray, 100 = white)
     a = 0   = neutral on green-red axis
     b = 0   = neutral on blue-yellow axis
     → pure white */
}

/* Variations: */
.consent-text { color: lab(100% 0 0); }     /* L as percentage */
.consent-text { color: lch(100 0 0); }      /* LCH: Lightness, Chroma, Hue — same white */
.consent-text { color: oklab(1 0 0); }      /* OKLab: lightness=1 = white */

/* Background-matching non-white in Lab: */
.consent-text { color: lab(97 -1.5 3); }
/* If page background is lab(97 -1.5 3) — a specific off-white — exact match = invisible */

/* Lab color math insight:
   In the sRGB gamut, lab(100 0 0) = white = rgb(255,255,255)
   Values outside sRGB gamut (a,b outside ±128 range) may gamut-map differently
   but for erasure attacks, L=100 with neutral a,b always produces white

   Detection: same computed-style normalization approach
   getComputedStyle(consentTextEl).color → 'rgb(255, 255, 255)'

   Note: browsers may represent out-of-gamut colors differently;
   for in-gamut white (L=100, a=0, b=0) normalization to rgb(255,255,255) is reliable */

LCH and OKLab are additional surfaces: CIE Lab, LCH (Lightness Chroma Hue polar form of Lab), OKLab, and OKLCH all have white points reachable via lab(100 0 0), lch(100 0 0), oklab(1 0 0), and oklch(1 0 0) respectively. Each is a distinct syntactic form; the common defense is computed-style normalization.

CRITICAL
SA-CSS-HSL-003 — color: lab(100 0 0): white in CIE Lab color space; maximum audit coverage gap; also covers lch(100 0 0) and oklab(1 0 0) variants. Detection: computed style normalization to rgb(255,255,255) → CRITICAL.

SA-CSS-HSL-004: color: color(display-p3 1 1 1) — white in display-p3 wide gamut

The color() function selects a named color space and provides channel values. color(display-p3 1 1 1) specifies R=1, G=1, B=1 in the display-p3 color space — the equivalent of RGB maximum in a wide-gamut space, which is white. Display-p3 is widely supported on modern macOS and iOS displays (Apple has shipped display-p3 screens since 2015). The color() syntax is unfamiliar to legacy CSS audit tools; the channel values 1 1 1 may resemble opacity or transform values to a human reviewer unfamiliar with the function.

/* Attack: white in display-p3 wide-gamut color space */
.consent-text {
  color: color(display-p3 1 1 1);
  /* color(colorspace r g b):
     display-p3: wide-gamut RGB color space (Apple, DCI-P3)
     R=1, G=1, B=1 = white in display-p3
     (1 1 1 in display-p3 ≈ 255 255 255 in sRGB for the white point) */
}

/* Other color() space whites: */
.consent-text { color: color(srgb 1 1 1); }         /* sRGB white via color() function */
.consent-text { color: color(srgb-linear 1 1 1); }  /* linear-light sRGB white */
.consent-text { color: color(a98-rgb 1 1 1); }      /* Adobe RGB white */
.consent-text { color: color(prophoto-rgb 1 1 1); } /* ProPhoto RGB white */
.consent-text { color: color(rec2020 1 1 1); }      /* Rec. 2020 wide gamut white */

/* Background mismatch consideration:
   display-p3 white and sRGB white are the same perceptual white.
   However, if an attacker uses a color(display-p3 ...) value that is
   IN the display-p3 gamut but OUTSIDE sRGB, the browser will gamut-map
   when rendering on sRGB displays — this creates potential for near-white
   colors that evade sRGB-normalized comparison.

   For white (1 1 1) in any color space: normalization to rgb(255,255,255) is reliable.

   Detection:
   getComputedStyle(consentTextEl).color
   → 'rgb(255, 255, 255)' for all white-equivalent values including display-p3 1 1 1

   Comprehensive color-erased-against-background check:
   function isColorMatchingBackground(el) {
     const textColor = getComputedStyle(el).color;
     let ancestor = el.parentElement;
     while (ancestor) {
       const bg = getComputedStyle(ancestor).backgroundColor;
       if (bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent') {
         // Found nearest non-transparent background
         return textColor === bg; // exact match = invisible
       }
       ancestor = ancestor.parentElement;
     }
     return false;
   } */

The universal defense: compare computed text color to computed background color: Across all four SA-CSS-HSL patterns, the correct detection strategy is not recognizing any specific color format but comparing the resolved text color against the resolved background color of the nearest opaque ancestor. If they match, the text is invisible. This check is syntax-agnostic and covers all present and future color format obfuscation attacks.

CRITICAL
SA-CSS-HSL-004 — color: color(display-p3 1 1 1): white in display-p3; extends to all color() space whites; computed normalization to rgb(255,255,255) catches this. Detection: compare computed text color to nearest opaque background color → CRITICAL.

Summary of SA-CSS-HSL findings

IDSeverityColor formatWhite expressionDetection
SA-CSS-HSL-001 CRITICAL HSL hsl(0, 0%, 100%) getComputedStyle.color → rgb(255,255,255); compare to background
SA-CSS-HSL-002 CRITICAL OKLCH oklch(1 0 0) getComputedStyle normalization; background comparison
SA-CSS-HSL-003 CRITICAL CIE Lab / LCH / OKLab lab(100 0 0) getComputedStyle normalization; background comparison
SA-CSS-HSL-004 CRITICAL color() function (display-p3, sRGB, ProPhoto, etc.) color(display-p3 1 1 1) getComputedStyle normalization; universal background-match check

Related pages