← Blog

MCP server CSS filter:blur and filter:grayscale consent security

Four SA-CSS-BLR consent attack patterns that use CSS filter functions to make consent text visually unreadable, remove urgency signals from warning text, or produce black-on-black invisibility via inversion. Unlike color or opacity attacks, CSS filter attacks preserve the element's computed color property — getComputedStyle(el).color reports the correct dark text color even when the text is spatially blurred to the point of complete illegibility. This is the core reason filter-based attacks evade first-generation consent auditors: the auditable CSS property (color) is correct; the unaudited property (filter) destroys the visual result.

By SkillAudit  |  30 Oct 2026

CSS filter functions apply pixel-level transformations to an element's rendered output after it has been painted. getComputedStyle does reflect the filter property — getComputedStyle(el).filter returns the filter string — but most consent audit engines do not inspect this property, making filter attacks a low-detection-rate vector in current tooling.

SA-CSS-BLR-001: filter:blur(10px) — consent text spatially unreadable

filter: blur(10px) on consent text applies a Gaussian blur with a 10-pixel sigma to the element's rendered pixels. The text glyphs are spread into overlapping halos: individual letterforms become indistinguishable at standard reading distances (60–80cm from a desktop monitor). The WCAG contrast ratio calculation uses the CSS color and background-color computed values — neither of which changes under filter: blur. A black-on-white consent text with 21:1 contrast ratio, blurred to complete illegibility, still reports 21:1 to a WCAG auditor.

.consent-text {
  color: #111111;          /* dark, WCAG compliant: 18.4:1 contrast */
  background: white;
  filter: blur(10px);      /* text pixels spread to illegible halo */
}

/* WCAG automated checker result:
   Contrast ratio: 18.4:1  ✓ passes AAA
   Color: rgb(17, 17, 17)  ✓ no anomaly
   Opacity: 1              ✓ no anomaly
   Visibility: visible     ✓ no anomaly
   → NO violations reported

   Visual result: completely illegible blur at 10px sigma

   Detection:
   getComputedStyle(el).filter → contains 'blur(' → parse sigma value
   sigma > 2px on text element with font-size < 24px → CRITICAL
   sigma > 1px on text element with font-size < 12px → CRITICAL */

WCAG passes, text is illegible: The WCAG contrast algorithm is defined in terms of CSS computed color values, not pixel-level readability. A filter: blur(40px) on white text would cause the text to disappear entirely into the background — the blur radius exceeds the glyph size — while reporting a passing contrast ratio based on the underlying color values. No WCAG automated checker today measures blur sigma against readability.

CRITICAL
SA-CSS-BLR-001 — filter: blur(10px) on consent text: spatially illegible at standard reading distance; WCAG contrast passes on underlying color. Detection: getComputedStyle(el).filter contains blur( with sigma > 2px on normal body text → CRITICAL.

SA-CSS-BLR-002: filter:blur(2px) on 8px font — small text threshold

The threshold at which blur causes illegibility scales with font size. Large display text (72px) at filter: blur(2px) is still legible: the 2px spread is small relative to the glyph height. Small body text at 8px with filter: blur(2px) is at the exact threshold where hairline glyph strokes (which may be 0.5–1px wide in the rendered font) spread to the full glyph height. The detection threshold must be relative: for a consent text element with small font size, a smaller blur radius is sufficient to cause illegibility.

.consent-text {
  font-size: 8px;         /* already at the lower end of readability */
  color: #333;
  filter: blur(2px);      /* 2px blur on 8px font: glyph strokes fully spread */
}

/* Why this is used compound:
   font-size: 8px alone: technically readable under magnification → MEDIUM
   filter: blur(2px) on font-size:14px: mildly soft → LOW
   font-size: 8px + filter: blur(2px): compound → letters indistinguishable → CRITICAL

   Blur-to-font-size ratio detection:
   const fontSize = parseFloat(getComputedStyle(el).fontSize);
   const blurMatch = getComputedStyle(el).filter.match(/blur\((\d+\.?\d*)px\)/);
   if (blurMatch) {
     const sigma = parseFloat(blurMatch[1]);
     const ratio = sigma / fontSize;
     if (ratio > 0.2) → CRITICAL (blur exceeds 20% of font height)
   } */
CRITICAL
SA-CSS-BLR-002 — blur sigma > 20% of font size: compound illegibility; hairline strokes spread across full glyph height. Detection: sigma / fontSize > 0.2 → CRITICAL.

SA-CSS-BLR-003: filter:grayscale(1) — urgency signal removal from warning text

filter: grayscale(1) converts all color values in the filtered element to their luminance-equivalent grayscale. Red warning text — which carries high urgency signal due to the cultural and neurological association of red with danger — becomes a neutral gray with identical luminance. The consent text is still legible (no blur, no hidden pixels), but the visual urgency signal that would cause a user to read carefully and pause before proceeding is removed. A consent dialog with red warning text about “granting permanent file access” at 16:1 contrast becomes a gray-on-white text that reads as routine informational text.

.consent-warning {
  color: #dc2626;          /* red warning: urgency signal to user */
  filter: grayscale(1);    /* red → gray: urgency signal removed */
}

/* After grayscale filter:
   Visual color: approximately rgb(75, 75, 75) luminance equivalent
   Computed getComputedStyle(el).color: still rgb(220, 38, 38) (the CSS color)
   → CSS color check: red, no anomaly
   → Visual rendering: gray, urgency removed

   The deception is semantic: not hiding the text, but removing the psychological
   signal that would cause the user to read it attentively.

   Detection:
   getComputedStyle(el).filter contains 'grayscale(1)' or 'grayscale(100%)'
   AND el color is a non-neutral hue (hue saturation > 30%) → HIGH
   Reasoning: grayscale on text that was intentionally colored for semantic emphasis
   removes that semantic emphasis; on already-neutral text → LOW */

Urgency manipulation is consent manipulation: The color red on warning text is not decoration — it is semantic information about severity. A user who reads gray-on-white text labeled “you authorize permanent file access” has a different cognitive and behavioral response than a user who reads the same text in red. Stripping urgency signal while preserving readability is a consent attack at the semantic layer, not the visual layer.

HIGH
SA-CSS-BLR-003 — filter: grayscale(1) on semantically colored warning text: urgency signal removed; CSS color remains red but visual rendering is gray. Detection: grayscale in filter + CSS color hue saturation > 30% → HIGH.

SA-CSS-BLR-004: filter:invert(1) on white text in white container — black-on-black

filter: invert(1) on an element inverts all pixel values: white (#fff = rgb(255,255,255)) becomes black (#000), dark text becomes light text, and the entire color map is reversed. The attack exploits a specific scenario: an element with white text (color: white) inside a white container. Applied naively, filter: invert(1) would produce black text on a still-white background — VISIBLE. But when the filter is applied to the consent text element (not just the text node), the element's background is also inverted: the white background becomes black. The result is black text on a black background — invisible.

.consent-text {
  color: white;            /* white text */
  background: white;       /* white background — identical, hidden as baseline */
  /* Without filter: white text on white background → already invisible */
  /* This is the baseline invisible state */
  filter: invert(1);       /* inverts: text → black; background → black */
  /* Result: black text on black background → still invisible */
}

/* More subtle compound variant: */
.consent-dialog {
  background: white;  /* dialog background: white */
}
.consent-text {
  color: #eee;        /* near-white text — barely visible on white */
  /* getComputedStyle: color = rgb(238,238,238) — detectable as near-white */
  filter: invert(1);  /* #eee inverts to #111 (dark) */
  /* BUT: filter also inverts the local background rendering → white → black */
  /* Result: #111 text on black background → invisible */
}

/* Detection:
   getComputedStyle(el).filter contains 'invert(' → check inversion amount
   invert(1) or invert(100%) on any text element → HIGH
   Combined with color that is near-white (lightness > 80%) → CRITICAL
   (near-white text + invert = near-black text on near-black background) */
CRITICAL
SA-CSS-BLR-004 — filter: invert(1) on near-white text in white container: text color inverts to dark, background also inverts to dark → dark-on-dark invisible. Detection: invert(1) in filter + CSS color lightness > 80% → CRITICAL.

Summary of SA-CSS-BLR findings

IDSeverityFilter functionAttack mechanismDetection
SA-CSS-BLR-001 CRITICAL blur(10px) Text spatially spread to illegible halo; WCAG contrast passes on underlying color filter contains blur() with sigma > 2px on body text
SA-CSS-BLR-002 CRITICAL blur(2px) on 8px font Blur sigma exceeds 20% of font height; hairline strokes spread to full glyph sigma / fontSize > 0.2
SA-CSS-BLR-003 HIGH grayscale(1) Removes red/colored urgency signal; CSS color unchanged; semantic emphasis lost grayscale in filter + CSS color hue saturation > 30%
SA-CSS-BLR-004 CRITICAL invert(1) Near-white text + container background both inverted to near-black; invisible invert(1) in filter + CSS color lightness > 80%

Related pages