Security reference · CSS injection · backdrop-filter · Consent manipulation

MCP server CSS backdrop-filter consent security — glassmorphism blur consent obscuration

CSS backdrop-filter applies visual post-processing effects (blur, brightness, contrast, saturate, grayscale) to the content behind a positioned element. A nearly transparent overlay with backdrop-filter: blur(10px) placed over consent text renders the text as an unreadable frosted-glass smear — the consent text element's own computed color, opacity, and visibility are all correct. The obscuration occurs at the GPU compositing layer after the text is rasterized, making it invisible to every property-inspection consent auditor that checks the consent text element's own styles.

backdrop-filter attack surface overview

Attack ID backdrop-filter value Visual effect on consent Audit blind spot
SA-CSS-BDF-001 blur(10px) Consent text rendered as unreadable blurred smear — frosted-glass appearance; individual characters indistinguishable at standard blur radius Consent text color, opacity, visibility, font-size all correct on the text element; blur is applied to the region behind the overlay, not the text element itself
SA-CSS-BDF-002 brightness(0) Area behind overlay rendered pitch black — consent text appears as dark characters on a black background (zero contrast) Consent text color is still #1a1a1a; auditor computes contrast against page background (#ffffff) and passes; actual rendered background is black due to backdrop brightness(0)
SA-CSS-BDF-003 contrast(0) Area behind overlay rendered as a flat gray — all contrast in the content behind the overlay is collapsed to a uniform gray, making consent text indistinguishable from the gray background Same as BDF-002 — contrast is computed between text color and page background, not text color and the actual rendered backdrop
SA-CSS-BDF-004 blur() applied via JS after page load Backdrop blur overlay injected post-render — appears correct at page load when static audits run; blur activated at install-button-hover or mousedown event Static and early-runtime audits check consent at page load; dynamic blur injection after user interaction is not captured

Compositing-layer attack: backdrop-filter is a GPU compositing operation that runs after all style properties (color, opacity, visibility) have been applied and the layer has been rasterized. It is not a CSS property of the consent text element — it is a property of an overlaid element that affects the rendering of everything behind it within its bounding box. An auditor that checks properties of the consent text element (the standard approach) will never find the issue. Detection requires: (1) finding elements positioned over consent text, and (2) checking those elements for backdrop-filter values. This is hit-testing plus style inspection — two levels of indirection from the consent text itself.

Background: how CSS backdrop-filter works

CSS backdrop-filter applies filter effects to the area behind an element — the "backdrop" — rather than to the element's own content. The effect is computed by: taking a snapshot of the rendered content behind the element (within the element's bounding box), applying the specified filter functions to that snapshot, and compositing the filtered snapshot with the element's own content (background, text, etc.). The element's own opacity controls how much of the element's content is visible; the filtered backdrop is always visible through any transparent or semi-transparent areas of the element's background. A fully transparent element (background: transparent, opacity: 1) with backdrop-filter: blur(10px) would be completely invisible as an element but would still apply a blur to everything visible behind it within its bounding box.

Browser support: Chrome 76+, Firefox 103+, Safari 9+ (with -webkit- prefix). The -webkit-backdrop-filter prefix is still required on Safari as of 2026 for some filter functions. SkillAudit checks both prefixed and unprefixed versions.

Attack 1: backdrop-filter: blur(10px) frosted-glass consent obscuration (SA-CSS-BDF-001)

A Gaussian blur of 10px applied to a 14px font-size text makes individual characters indistinguishable. The blur radius relative to the font size is 0.7em — roughly equivalent to defocusing the text to the point where letter shapes merge into unreadable smears. A nearly-transparent overlay (e.g., background: rgba(255,255,255,0.05)) with backdrop-filter: blur(10px) positioned absolutely over the consent container creates this frosted-glass effect. The overlay is 95% transparent, so it appears visually indistinguishable from a design-pattern glassmorphism card. The consent text behind it is blurred into illegibility.

/* Attack: backdrop-filter:blur() over consent text — frosted-glass obscuration */

/* HTML structure:
   <div class="consent-container">
     <p class="consent-text">By installing this server you authorize...</p>
     <div class="frosted-overlay"></div>
   </div>
*/

.consent-container {
  position: relative;
}

.frosted-overlay {
  position: absolute;
  inset: 0;
  background: rgba(255, 255, 255, 0.05); /* nearly transparent — looks like a design element */
  -webkit-backdrop-filter: blur(10px);    /* Safari */
  backdrop-filter: blur(10px);            /* Chrome, Firefox */
  border-radius: 8px;                     /* rounded corners — looks like a card */
  /* The overlay is 5% opaque — almost invisible as an element.
     But the backdrop-filter:blur(10px) applies a Gaussian blur to everything
     behind the overlay within the overlay's bounding box.
     All consent text behind the overlay is blurred into an unreadable smear.

     Property audit of consent text element (.consent-text):
     color: #1a1a1a → dark, legible ✓
     opacity: 1 → fully opaque ✓
     visibility: visible → visible ✓
     font-size: 14px → readable size ✓
     background-color: transparent → no background ✓
     Contrast ratio: #1a1a1a on white (#fff) → 18.1:1 → AAA ✓
     Every consent text property PASSES.

     Actual rendering:
     Consent text is rendered correctly but the backdrop blur renders the
     region behind the overlay (including the consent text) as a blurred smear.
     The blurred smear replaces the sharp text in the user's view. */
}

/* Minimal blur radius for illegibility at different font sizes:
   font-size: 10px → blur(3px) = 0.3em — text blurs at edges, hard to read
   font-size: 12px → blur(6px) = 0.5em — words distinguishable, characters blur
   font-size: 14px → blur(8px) = 0.57em — characters indistinguishable
   font-size: 16px → blur(10px) = 0.625em — text fully illegible */

/* Variant: blur applied to the consent container itself */
.consent-container-blur {
  /* backdrop-filter on the container blurs everything behind the container itself.
     If the container is positioned over other content, the content is blurred.
     But more commonly this is used to blur behind a dialog card.
     Not the primary attack vector for consent text — use overlay approach. */
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}

SA-CSS-BDF-001 (High). The frosted-glass attack exploits the compositing-layer gap between the consent text element's own properties (correct) and the rendered visual output (blurred). SkillAudit detects this by running document.elementFromPoint() at the center of each consent element and checking any interposing element for backdropFilter and webkitBackdropFilter computed values. Any element positioned over consent text with a backdrop blur radius > 3px relative to the consent font size is flagged as a high-severity finding.

/* Detection: backdrop-filter overlay over consent text */
function detectBackdropFilterConsentObscuration() {
  const CONSENT_KEYWORDS = ['authorize', 'grant', 'access', 'permission',
                            'agree', 'terms', 'third-party', 'transmit'];
  const findings = [];

  document.querySelectorAll('*').forEach(el => {
    const text = el.textContent.toLowerCase();
    if (!CONSENT_KEYWORDS.some(k => text.includes(k))) return;
    if (el.textContent.trim().length < 30) return;

    const rect = el.getBoundingClientRect();
    if (rect.width === 0 || rect.height === 0) return;

    // Hit-test: find what element is on top of the consent element center
    const centerX = rect.left + rect.width / 2;
    const centerY = rect.top + rect.height / 2;

    // elementsFromPoint returns all elements at this coordinate, top to bottom
    const elementsAtPoint = document.elementsFromPoint(centerX, centerY);

    elementsAtPoint.forEach(topEl => {
      if (topEl === el || el.contains(topEl)) return;

      const topCs = getComputedStyle(topEl);
      const backdropFilter = topCs.backdropFilter || topCs.webkitBackdropFilter || '';

      if (!backdropFilter || backdropFilter === 'none') return;

      // Parse blur radius from backdrop-filter value
      const blurMatch = backdropFilter.match(/blur\(\s*([\d.]+)(px|rem|em)?\s*\)/);
      if (!blurMatch) {
        // Other backdrop-filter functions (brightness, contrast, etc.)
        findings.push({
          vuln: 'SA-CSS-BDF-002',
          severity: 'HIGH',
          element: topEl,
          consentElement: el,
          detail: `backdrop-filter:${backdropFilter} on element over consent text; ` +
                  `non-blur backdrop filter may alter rendering of consent text behind it`
        });
        return;
      }

      const blurPx = parseFloat(blurMatch[1]);
      const consentFontSize = parseFloat(getComputedStyle(el).fontSize) || 14;
      const blurRatio = blurPx / consentFontSize;

      const severity = blurRatio > 0.5 ? 'HIGH' : 'MEDIUM';

      findings.push({
        vuln: 'SA-CSS-BDF-001',
        severity,
        element: topEl,
        consentElement: el,
        detail: `backdrop-filter:blur(${blurPx}px) on element positioned over consent text (font-size:${consentFontSize}px); ` +
                `blur-to-font ratio: ${blurRatio.toFixed(2)} — consent text rendered as ` +
                `${blurRatio > 0.5 ? 'unreadable blur' : 'reduced-legibility blur'}; ` +
                `consent element own color/opacity/visibility properties are all correct — ` +
                `backdropFilter on overlay is not detected by property-inspection auditors`
      });
    });
  });

  return findings;
}

Attack 2: backdrop-filter: brightness(0) blackout (SA-CSS-BDF-002)

backdrop-filter: brightness(0) sets the brightness of the area behind the overlay to 0% — pitch black. Consent text rendered on a white background with dark text normally has a WCAG contrast ratio of ~18:1. After brightness(0), the area behind the overlay is black. Dark consent text on a black background has a contrast ratio of approximately 1:1 — completely unreadable. An auditor computing contrast ratio for the consent text gets the color of the text element (#1a1a1a) and the color of the page background (#ffffff), computing 18:1. The auditor never reads the actual rendered background color behind the consent text (which is now black due to the backdrop filter).

/* Attack: backdrop-filter:brightness(0) blackout over consent */
.blackout-overlay {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0);   /* fully transparent — overlay is invisible */
  -webkit-backdrop-filter: brightness(0);
  backdrop-filter: brightness(0);
  /* brightness(0) = everything behind overlay is rendered at 0% brightness = black
     Transparent overlay + brightness(0) backdrop:
     The overlay itself is invisible (transparent background).
     But the backdrop filter renders the content behind it at 0% brightness.
     Consent text: #1a1a1a on white (18:1 contrast) → #1a1a1a on black (≈1:1 contrast)
     Visual result: dark text on dark background → unreadable */
}

/* Auditor contrast check:
   const consentEl = document.querySelector('.consent-text');
   const color = getComputedStyle(consentEl).color;         // → '#1a1a1a' (correct)
   const bgColor = getComputedStyle(consentEl).backgroundColor; // → 'transparent'
   const parentBg = getComputedStyle(consentEl.parentElement).backgroundColor; // → 'white'
   const contrast = computeWCAGContrast(color, parentBg);  // → 18.1:1 → PASS
   // The auditor never reads the backdrop-filter on the overlay element above consent.
   // The actual rendered background of consent text is black — contrast ≈ 1:1. */

/* Variant: brightness(0.1) partial blackout */
.dim-overlay {
  position: absolute;
  inset: 0;
  background: transparent;
  backdrop-filter: brightness(0.1);
  /* brightness(0.1) = 10% brightness behind overlay
     White background becomes near-black (#191919).
     Dark consent text (#1a1a1a) on near-black (#191919): contrast ≈ 1.005:1
     Completely unreadable. */
}

/* Variant: contrast(0) flat gray */
.contrast-zero-overlay {
  position: absolute;
  inset: 0;
  background: transparent;
  backdrop-filter: contrast(0);
  /* contrast(0) = all pixels behind overlay become the average color of the backdrop.
     For white + black text: average ≈ medium gray.
     Dark text on gray: low contrast. White text on gray: low contrast.
     Result: consent text is faintly visible as gray shapes on gray background. */
}

Attack 3: backdrop-filter: contrast(0) flat gray consent (SA-CSS-BDF-003)

backdrop-filter: contrast(0) collapses all contrast in the backdrop to a uniform gray, calculated as the average luminance of the affected region. For a white page with dark consent text, the affected region contains white pixels and dark text pixels. The average luminance results in a medium gray background, and the text becomes indistinguishable gray shapes on a gray background. The consent text characters may be faintly perceptible as slightly-different-gray forms but are not readable. The property-inspection contrast audit passes because it measures the text's CSS color against the page background — neither of which changes.

Attack 4: backdrop blur injected via JS after install-button hover (SA-CSS-BDF-004)

A static audit that runs at page load would not find a dynamically injected backdrop blur overlay. An attacker's script listens for mouseover on the install button and injects a backdrop-filter overlay over the consent text at that moment. The user hovers to click the install button, the consent text blurs just before they look at it to confirm, and they click without being able to read the freshly blurred consent. After the click, the overlay may be removed — a post-install audit finds no backdrop filter. The attack window is the hover-to-click interaction.

/* Attack: backdrop blur injected on install-button hover */
const installBtn = document.querySelector('.install-button');
const consentContainer = document.querySelector('.consent-container');

installBtn.addEventListener('mouseover', () => {
  // Inject backdrop blur overlay over consent container
  const overlay = document.createElement('div');
  overlay.className = 'blur-overlay';
  overlay.style.cssText = `
    position: absolute;
    inset: 0;
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    background: rgba(255,255,255,0.02);
    pointer-events: none;  /* allow clicks to pass through to install button */
  `;
  consentContainer.style.position = 'relative';
  consentContainer.appendChild(overlay);
});

// Clean up after click (remove evidence)
installBtn.addEventListener('click', () => {
  const overlay = consentContainer.querySelector('.blur-overlay');
  if (overlay) overlay.remove();
});

/* Detection approach for dynamic injection:
   1. Set up MutationObserver on consent container before page load
   2. Watch for children with backdrop-filter
   3. Log any injection event with timestamp */

function watchForDynamicBackdropFilter(consentEl) {
  const observer = new MutationObserver((mutations) => {
    mutations.forEach(mutation => {
      mutation.addedNodes.forEach(node => {
        if (!(node instanceof Element)) return;
        const cs = getComputedStyle(node);
        const bf = cs.backdropFilter || cs.webkitBackdropFilter || '';
        if (bf && bf !== 'none') {
          console.error('[SkillAudit] SA-CSS-BDF-004: backdrop-filter element injected over consent:',
                        bf, 'element:', node);
          // Disable install button immediately
          document.querySelectorAll('.install-button, [data-install]')
                  .forEach(btn => btn.disabled = true);
        }
      });
    });
  });
  observer.observe(consentEl, { childList: true, subtree: true });
  return observer;
}

SkillAudit detection

HIGH SA-CSS-BDF-001: element with backdrop-filter: blur(N px) positioned over consent text — blur radius > 0.5× the consent font size renders text illegible. SkillAudit uses document.elementsFromPoint() at the consent element center and checks each interposing element for prefixed and unprefixed backdropFilter blur values.
HIGH SA-CSS-BDF-002 / BDF-003: element with backdrop-filter: brightness(0) or contrast(0) over consent text — the actual rendered background behind consent collapses to black or flat gray, making dark consent text unreadable despite the CSS contrast audit passing 18:1 against the page background. SkillAudit flags all non-normal backdrop-filter function values on elements positioned over consent text.
HIGH SA-CSS-BDF-004: backdrop-filter overlay injected dynamically via mouseover on the install button — the attack window is the hover-to-click interaction; static and early-runtime audits miss it. SkillAudit sets up a MutationObserver on consent containers that watches for dynamically injected children with backdropFilter values throughout the session.

Run SkillAudit to detect SA-CSS-BDF patterns in any MCP server before install. SkillAudit's hit-test scanner checks elements positioned over consent text for both static and dynamically-injected backdrop-filter values, detecting frosted-glass blur, brightness blackout, and contrast collapse attacks that are invisible to property-inspection auditors checking only the consent text element itself.