CSS backdrop-filter as a Consent Bypass Vector in MCP Server UIs

backdrop-filter applies GPU compositing effects — blur, brightness, contrast — to the region behind a positioned overlay, not to the overlay's own content. An attacker places a nearly invisible overlay over a consent dialog and gives it backdrop-filter: blur(10px): the consent text is blurred into an unreadable frosted-glass smear. Every property audit of the consent text element itself passes — its color, opacity, visibility, and font-size are all correct. The obscuration exists entirely at the GPU compositing layer, two levels of indirection below where every standard consent auditor looks. This post maps the full SA-CSS-BDF attack surface, explains exactly why no current WCAG or security scanner catches it, and provides the hit-test-based JavaScript detection algorithm SkillAudit uses to find overlaying elements with backdrop-filter values.

The GPU compositing model: how backdrop-filter works

To understand why backdrop-filter is invisible to property-inspection auditors, you need to understand where in the browser rendering pipeline it operates. The browser renders a page in stages:

  1. Style resolution: each element's computed style values are resolved from the cascade, inheritance, and custom properties
  2. Layout: the box tree is computed — each element's position, size, and stacking order
  3. Paint: each paint layer is rasterized to a bitmap — text characters are drawn at their computed color, backgrounds are filled, etc.
  4. Compositing: the individual paint layers are assembled on the GPU into the final screen image

Most CSS properties operate at stages 1–3. color, opacity, visibility, background-color — these are all resolved and applied during style resolution and paint. getComputedStyle() reports the values from stage 1 — the resolved style.

backdrop-filter operates at stage 4 — GPU compositing. The filter is not applied to the element's own paint output. Instead, it is applied to the GPU texture that contains the rendered content of all layers behind the element within the element's bounding box. The browser: (1) takes a snapshot of the composited content behind the element, (2) applies the filter function (blur, brightness, contrast, etc.) to that snapshot, (3) renders the filtered snapshot as the background region visible through the element's transparent or semi-transparent areas. The element's own painted content — its text, borders, backgrounds — is completely unaffected and will still show with their computed styles.

Critical implication: an auditor that calls getComputedStyle(consentElement) and inspects color, opacity, visibility, and fontSize will find all correct values — because those values are correct. The consent text was painted correctly. It's the compositing layer below the overlay that was modified. Standard property-inspection auditing is architecturally blind to this attack class.

A key distinction to understand: filter (the non-backdrop version) does apply to the element's own rendered output. An element with filter: blur(10px) blurs its own content; getComputedStyle(el).filter returns "blur(10px)"; the property is findable by property inspection on the element itself. backdrop-filter on an overlaid element is invisible when you inspect the consent element's own properties — you have to find the overlay and inspect its properties instead. For a broader comparison of these two attack surfaces, see the CSS backdrop-filter consent attack reference page and the CSS filter consent bypass post which covers the element-level filter attacks.

Why backdrop-filter exists and why glassmorphism enables cover

backdrop-filter was designed for frosted-glass UI patterns — the translucent card aesthetic popularized by iOS and macOS, where a content card appears to blur the background behind it. Glassmorphism became a widespread UI trend: navigation bars that blur the page scrolling behind them, modal dialogs that frost their backgrounds, tooltips with a subtle translucent glow. The aesthetic is so common that a semi-transparent overlay with backdrop-filter: blur() applied is instantly recognizable as a design element, not a security threat.

This legitimacy is exactly what makes the attack effective as a social engineering layer. A malicious MCP server UI that places a glassmorphism-styled overlay over its consent dialog looks like an intentional design decision. The frosted blurring of the consent text looks like a depth-of-field effect intended to focus the user's attention on the install button in the foreground. A casual reviewer would have to specifically know to look for the overlay element, check its backdrop-filter value, and understand that a 10px blur applied to 14px text renders the characters indistinguishable — before they would identify it as an attack.

For the full picture of how MCP server UIs exploit CSS visual design patterns to bypass consent, see our overview post why CSS has no security model for agent consent.

SA-CSS-BDF-001: blur() frosted-glass consent obscuration (High)

The primary attack: a nearly transparent overlay element is positioned absolutely over the consent container with backdrop-filter: blur(10px). The overlay itself is essentially invisible — its background might be rgba(255,255,255,0.05), making it 5% opaque and visually imperceptible as a distinct element. What the overlay does is apply a 10px Gaussian blur to everything rendered behind it — including the consent text.

SA-CSS-BDF-001: blur(10px) frosted-glass overlay (High)

A frosted-glass overlay is positioned absolutely over the consent container. Its opacity is close to zero; it blends with the UI. The backdrop-filter: blur(10px) property applies a Gaussian blur to the compositing layer behind the overlay. At font-size 14px, a 10px blur (0.71em) makes individual glyph shapes indistinguishable. The consent text element's computed color, opacity, and visibility all pass. The text is present in the DOM, focusable, submittable. The user sees a blurred smear where the consent text should be.

/* SA-CSS-BDF-001: backdrop-filter:blur() frosted-glass consent obscuration */

/* HTML structure:
   <div class="consent-container">
     <p class="consent-text">By installing this MCP server you authorize
       read access to your filesystem, network access, and permission to
       execute arbitrary shell commands on your machine.</p>
     <div class="glass-overlay"></div>  <!-- the attacker element -->
     <button class="install-btn">Install now</button>
   </div>
*/

.consent-container {
  position: relative;   /* establishes containing block for absolute overlay */
}

.glass-overlay {
  position: absolute;
  inset: 0;             /* covers entire consent container */
  background: rgba(255, 255, 255, 0.05);  /* 5% opaque — visually imperceptible */
  -webkit-backdrop-filter: blur(10px);    /* Safari prefix — required through Safari 17.x */
  backdrop-filter: blur(10px);            /* Chrome 76+, Firefox 103+ */
  border-radius: 8px;                     /* rounded corners look like a card element */
  pointer-events: none;                   /* clicks pass through to install button */
  z-index: 1;                             /* on top of consent text */
}

/* Why property inspection of .consent-text finds nothing wrong:
   getComputedStyle(.consent-text).color        → "#1a1a1a"  → legible ✓
   getComputedStyle(.consent-text).opacity       → "1"         → fully opaque ✓
   getComputedStyle(.consent-text).visibility    → "visible"   → visible ✓
   getComputedStyle(.consent-text).fontSize      → "14px"      → readable ✓
   getComputedStyle(.consent-text).display       → "block"     → displayed ✓
   getComputedStyle(.consent-text).filter        → "none"      → no element filter ✓
   getComputedStyle(.consent-text).backdropFilter → "none"     → no backdrop filter ✓

   The blur is on .glass-overlay, not .consent-text.
   Standard consent auditors never check .glass-overlay. */

The minimum blur radius for illegibility scales with font size. At 14px body text (the common size for consent dialogs), a 10px blur (0.71em) produces indistinguishable glyph shapes. At smaller consent text:

Font sizeMin blur for illegibilityBlur/font ratio
10pxblur(3px)0.30em — characters blur at edges
12pxblur(6px)0.50em — words distinguishable, characters blur
14pxblur(8px)0.57em — characters indistinguishable
16pxblur(10px)0.625em — text fully illegible

A common attacker choice is blur(10px) regardless of font size — at any consent text size below 16px this is more than sufficient for full illegibility while still looking like a plausible glassmorphism design choice on any UI with large-text hero elements nearby.

SA-CSS-BDF-002: brightness(0) blackout (High)

Blur is the most visually obvious attack — a reviewer who looks closely will notice the smeared text. The brightness(0) variant produces a more subtle and arguably more effective result: the backdrop behind the overlay is rendered pitch black, and any dark-colored consent text (typically #1a1a1a or #333) becomes invisible by blending into the black backdrop.

SA-CSS-BDF-002: brightness(0) backdrop blackout (High)

An overlay with backdrop-filter: brightness(0) reduces the luminosity of everything behind it to zero — the backdrop becomes pure black. Dark consent text rendered on a black backdrop is 1:1 contrast. The WCAG 1.4.3 contrast audit computes contrast ratio between the text element's color property (e.g., #1a1a1a) and the background-color of the text element's own background (transparent → inherits white from page background). The audit sees #1a1a1a on #ffffff = 18.1:1 contrast — AAA. The actual rendered contrast is #1a1a1a on #000000 = 1.07:1 — invisible. The WCAG audit is comparing the wrong background.

/* SA-CSS-BDF-002: brightness(0) — blackout via backdrop-filter */
.blackout-overlay {
  position: absolute;
  inset: 0;
  background: rgba(255, 255, 255, 0.02);  /* nearly transparent */
  backdrop-filter: brightness(0);          /* renders region behind overlay as black */
  -webkit-backdrop-filter: brightness(0);
  pointer-events: none;
}

/* WCAG 1.4.3 audit of consent text element:
   text color:       #1a1a1a (dark gray)
   element bg:       transparent → inherits → #ffffff (white page background)
   contrast ratio:   #1a1a1a on #ffffff = 18.08:1 → AAA PASS ✓

   Actual rendering:
   backdrop-filter:brightness(0) renders the region behind the overlay as black
   text color:       #1a1a1a (dark gray, nearly black)
   actual rendered bg: #000000 (black)
   effective contrast: 1.07:1 → invisible

   The WCAG audit asks: "what is the background-color of this text element?"
   The answer is: "its own background is transparent, inheriting white from the page."
   The WCAG audit does not account for the opacity-1 overlay element with a
   brightness(0) backdrop-filter positioned on top of the text element.
   The effective background color at the rendered pixel level is not accessible
   via any CSS property of the consent text element itself. */

This variant is particularly dangerous because the attack surface is completely invisible to WCAG contrast ratio tooling. Unlike opacity-based consent attacks, where the consent element's own opacity is reduced, or color attacks where the foreground color is set to match the background, the brightness(0) backdrop attack leaves the consent element's contrast ratio computation completely valid while making the rendered output completely invisible.

SA-CSS-BDF-003: contrast(0) flat gray collapse (High)

Where brightness(0) collapses the backdrop to black, contrast(0) collapses it to a uniform mid-gray — approximately #808080. All luminosity differences in the content behind the overlay are compressed to zero, producing a flat gray plane. Both light and dark consent text become invisible against this gray backdrop: light text (#e0e0e0) on mid-gray has ~1.15:1 contrast; dark text (#1a1a1a) on mid-gray has ~5:1 contrast — technically just above the WCAG 4.5:1 minimum, but in practice completely illegible because the gray backdrop contains no structure for the eye to orient on, and the gray text blends into the gray background perceptually.

SA-CSS-BDF-003: contrast(0) uniform gray backdrop (High)

An overlay with backdrop-filter: contrast(0) renders the region behind it as a uniform gray (~#808080). All contrast in the consent text and its background is collapsed. With a dark page background and dark consent text, the text and background become the same shade of gray. With a light page background, dark text may retain marginal numerical contrast but becomes perceptually illegible against the structureless gray field. The WCAG audit of the consent text element itself computes contrast against the page's background-color, not the rendered gray backdrop.

/* SA-CSS-BDF-003: contrast(0) — flat gray backdrop */
.gray-overlay {
  position: absolute;
  inset: 0;
  background: rgba(128, 128, 128, 0.01);  /* nearly transparent */
  backdrop-filter: contrast(0);            /* collapses all contrast to uniform gray */
  -webkit-backdrop-filter: contrast(0);
  pointer-events: none;
}

/* The filter function reference:
   contrast(0)   → all colors → #808080 (zero contrast, flat gray)
   contrast(0.1) → colors moved 90% toward #808080
   contrast(0.2) → colors moved 80% toward #808080
   contrast(0.5) → half-contrast — still quite illegible at small text sizes

   Most consent text is 12–14px. At contrast(0.3) and 14px text, the
   character-shape contrast is insufficient for reliable reading. */

The contrast() function in backdrop-filter operates on the luminosity of the composited backdrop. It does not interact with the CSS contrast-color() function or with forced-colors media queries in the same way. An accessibility auditor checking for high-contrast mode compliance will not detect the contrast(0) backdrop manipulation — the manipulation is at the GPU layer, after the high-contrast system colors have already been applied. For related attacks that target accessibility modes specifically, see the forced-colors consent bypass reference.

SA-CSS-BDF-004: dynamic injection on mouseover / mousedown (High)

Static audit tools — automated scanners, pre-render snapshots, Lighthouse runs at page load — check the DOM and computed styles at a fixed point in time, typically immediately after the page has loaded or at a fixed delay. The dynamic injection pattern exploits this: the backdrop-filter overlay does not exist in the DOM at page load. It is injected by JavaScript in response to a user interaction event — specifically, when the user moves their mouse over or clicks the install button.

SA-CSS-BDF-004: backdrop-filter injected on install button hover (High)

The consent dialog is rendered normally at page load — all audits pass. When the user moves their mouse over the install button, a JavaScript handler injects a backdrop-filter overlay element over the consent text. The attack window is hover-to-click: the overlay exists from the moment the user positions their cursor on the install button until the moment they click. After the click (install initiated), the overlay is removed from the DOM — cleaning up the evidence. Any post-interaction audit finds no overlay and no backdrop-filter.

/* SA-CSS-BDF-004: dynamic backdrop-filter injection on install hover */

// The overlay does not exist at page load — no static audit will find it.
const installBtn = document.querySelector('.install-btn');
let blurOverlay = null;

installBtn.addEventListener('mouseenter', () => {
  // Create the backdrop-filter overlay just before the click
  blurOverlay = document.createElement('div');
  blurOverlay.style.cssText = `
    position: absolute;
    inset: 0;
    background: rgba(255, 255, 255, 0.03);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    pointer-events: none;
    z-index: 1;
  `;
  // Make sure consent container has position:relative
  document.querySelector('.consent-container').appendChild(blurOverlay);
});

installBtn.addEventListener('mouseleave', () => {
  // Clean up if user moves away without clicking
  if (blurOverlay) {
    blurOverlay.remove();
    blurOverlay = null;
  }
});

installBtn.addEventListener('click', () => {
  // Remove overlay immediately after click — no evidence post-interaction
  if (blurOverlay) {
    blurOverlay.remove();
    blurOverlay = null;
  }
  // ... proceed with install
});

/* Touch device variant: use 'touchstart' instead of 'mouseenter'.
   On mobile the user's first touch on the install button is both the
   hover-equivalent and the start of a click — use 'touchstart' and
   remove on 'touchend'. The attack window is milliseconds. */

Detecting this variant requires runtime monitoring rather than snapshot auditing. The detection must use a MutationObserver to watch for DOM insertions within the consent container subtree and check any newly inserted element for backdropFilter or webkitBackdropFilter values — see the detection section below.

Comparison: backdrop-filter vs filter in consent attacks

The filter and backdrop-filter properties are easy to conflate but operate at fundamentally different levels, which produces different detection strategies. Understanding this distinction is essential for building correct audit tooling:

Dimension filter (element-level) backdrop-filter (compositing-level)
What it filters The element's own rendered output — its text, backgrounds, borders The region behind the element — rendered content of layers below
Which element carries the property The consent text element itself (or its container) An overlaid element positioned over the consent text
Detection via consent element's computed style Yes — getComputedStyle(consentEl).filter returns the value No — consent element's backdropFilter is "none"; must inspect overlay
Detection method required Property inspection on consent element Hit-testing (elementsFromPoint) + property inspection on overlay element
Stacking context created Yes — filter creates a stacking context on the element Yes — backdrop-filter creates a stacking context on the overlay element
GPU compositing triggered Yes — element is promoted to a GPU layer Yes — overlay is promoted, and the backdrop snapshot is GPU-processed
CSP protection available No — no CSP directive controls filter values No — no CSP directive controls backdrop-filter values

The CSS filter consent bypass post covers the SA-CSS-FIL patterns in detail. The key distinction for audit tool authors: an auditor that only checks properties of the consent text element itself will catch filter attacks (since filter is on the consent element) but will miss backdrop-filter attacks (since backdrop-filter is on an overlaid element). Both checks are required for a complete consent audit.

Browser support matrix and the WebKit prefix situation

Unlike most modern CSS properties, backdrop-filter still requires the -webkit- vendor prefix on Safari as of late 2026. This creates a specific audit consideration: both prefixed and unprefixed versions must be checked to detect all real-world deployments of the attack.

Browser Unprefixed support Prefixed support Notes
Chrome 76+ (2019) -webkit-backdrop-filter accepted but not needed Widely deployed; a real-world attack must work in Chrome
Firefox 103+ (2022) Not recognized Preference was behind a flag until FF103; enabled by default in current versions
Safari Parsed but no effect in Safari 17.x -webkit-backdrop-filter required for effect Safari market share ~19%; attacker must use -webkit- prefix to target Safari users
Edge 79+ (follows Chrome) Accepted Same behavior as Chrome
iOS Safari No effect without prefix Supported via -webkit-backdrop-filter Important: mobile consent dialogs often appear on iOS Safari; prefix required

A correct detection algorithm must check both getComputedStyle(el).backdropFilter and getComputedStyle(el).webkitBackdropFilter. The WebKit-prefixed property may return a value while the unprefixed property returns "none" on Safari. SkillAudit's scanner checks both property names on every element in the hit-test path.

The filter function support matrix adds further nuance:

Filter function Chrome Firefox Safari Attack use case
blur() Full Full -webkit- prefix SA-CSS-BDF-001 frosted-glass obscuration
brightness() Full Full -webkit- prefix SA-CSS-BDF-002 blackout
contrast() Full Full -webkit- prefix SA-CSS-BDF-003 gray collapse
saturate(0) Full Full -webkit- prefix Grayscale backdrop — color consent cues removed
opacity() Full Full -webkit- prefix Fades backdrop — used with composited dark overlays

CSP scope: why Content Security Policy cannot protect against this

backdrop-filter values are CSS properties. There is no Content Security Policy directive that restricts CSS property values — not style-src, not style-src-elem, not any experimental directive. A strict CSP that blocks all inline styles using style-src 'nonce-xxx' can prevent inline style attributes from being injected, but an attacker can still:

The fundamental issue is that CSP was designed to restrict resource loading (scripts, stylesheets, images) and inline code execution, not to restrict which CSS visual effects can be applied to which elements. A complete consent bypass protection strategy cannot rely on CSP for backdrop-filter attacks. Runtime DOM monitoring and hit-test-based auditing are the only effective detection approaches.

Performance notes: backdrop-filter as a suspicious signal

GPU compositing is expensive. backdrop-filter forces the browser to: capture a composited snapshot of the backdrop region, apply the filter kernel on the GPU, and re-composite the result into the final frame — for every frame in which the overlay or its backdrop changes. For performance-sensitive UI components (scroll containers, animations), this can be a significant overhead. But consent dialogs have no performance justification for GPU compositing: they are static text, they do not animate, they do not scroll behind an overlay, and they do not contain any elements that would benefit from compositing layer promotion.

This creates a useful audit heuristic: a consent container that triggers GPU compositing — whether via backdrop-filter, will-change: transform, or filter on an overlaid element — is a suspicious signal regardless of the specific visual effect produced. Legitimate consent dialogs do not need GPU compositing. Any compositing hint on or adjacent to a consent element warrants closer inspection.

SkillAudit's scanner uses this heuristic as a primary signal: after identifying consent-text elements (via keyword matching on text content), it checks for GPU compositing triggers on the element, its ancestors, and all elements returned by document.elementsFromPoint() at the consent element's center coordinates.

Detection algorithm: hit-testing plus MutationObserver

A complete detection algorithm requires two phases: (1) static hit-test detection at page load, to catch pre-positioned overlays; (2) dynamic MutationObserver detection, to catch the dynamically injected SA-CSS-BDF-004 variant.

Phase 1: static hit-test detection

/* Phase 1: detect backdrop-filter overlays over consent text at page load */
function detectBackdropFilterConsentOverlays() {
  const CONSENT_KEYWORDS = [
    'authorize', 'grant', 'access', 'permission', 'agree', 'terms',
    'third-party', 'transmit', 'install', 'consent', 'allow'
  ];
  const MIN_CONSENT_LENGTH = 30;  // ignore short button labels
  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 < MIN_CONSENT_LENGTH) return;

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

    // Sample multiple points across the consent element
    const samplePoints = [
      { x: rect.left + rect.width / 2,  y: rect.top + rect.height / 2 },  // center
      { x: rect.left + rect.width / 4,  y: rect.top + rect.height / 3 },  // upper-left quad
      { x: rect.left + rect.width * 3/4, y: rect.top + rect.height * 2/3 } // lower-right quad
    ];

    samplePoints.forEach(({ x, y }) => {
      const elementsAtPoint = document.elementsFromPoint(x, y);

      elementsAtPoint.forEach(overlayEl => {
        // Skip the consent element itself and its ancestors
        if (overlayEl === el || el.contains(overlayEl) || overlayEl.contains(el)) return;

        const cs = getComputedStyle(overlayEl);
        const bdFilter = cs.backdropFilter || cs.webkitBackdropFilter || 'none';

        if (bdFilter !== 'none') {
          const severity = categorizeBackdropFilter(bdFilter);
          findings.push({
            id: severity.code,
            severity: severity.level,
            consentElement: el,
            overlayElement: overlayEl,
            backdropFilterValue: bdFilter,
            description: severity.description
          });
        }
      });
    });
  });

  return findings;
}

function categorizeBackdropFilter(value) {
  if (/blur\s*\(\s*([0-9.]+)px\s*\)/.test(value)) {
    const px = parseFloat(RegExp.$1);
    if (px >= 4) return { code: 'SA-CSS-BDF-001', level: 'HIGH',
      description: `backdrop-filter:blur(${px}px) over consent text — frosted-glass obscuration` };
  }
  if (/brightness\s*\(\s*0/.test(value)) {
    return { code: 'SA-CSS-BDF-002', level: 'HIGH',
      description: 'backdrop-filter:brightness(0) over consent text — blackout backdrop' };
  }
  if (/contrast\s*\(\s*0/.test(value)) {
    return { code: 'SA-CSS-BDF-003', level: 'HIGH',
      description: 'backdrop-filter:contrast(0) over consent text — flat gray backdrop' };
  }
  return { code: 'SA-CSS-BDF-UNK', level: 'MEDIUM',
    description: `Unknown backdrop-filter value over consent text: ${value}` };
}

Phase 2: MutationObserver for dynamic injection

/* Phase 2: MutationObserver for dynamically injected backdrop-filter overlays */
function watchForDynamicBackdropFilter() {
  const observer = new MutationObserver(mutations => {
    mutations.forEach(mutation => {
      mutation.addedNodes.forEach(node => {
        if (node.nodeType !== Node.ELEMENT_NODE) return;

        // Check the added node and all its descendants
        [node, ...node.querySelectorAll('*')].forEach(el => {
          const cs = getComputedStyle(el);
          const bdFilter = cs.backdropFilter || cs.webkitBackdropFilter || 'none';

          if (bdFilter !== 'none') {
            // Check if this element overlaps any consent text
            const rect = el.getBoundingClientRect();
            const elementsBelow = document.elementsFromPoint(
              rect.left + rect.width / 2,
              rect.top + rect.height / 2
            );

            const consentBelow = elementsBelow.find(below => {
              const text = (below.textContent || '').toLowerCase();
              return CONSENT_KEYWORDS.some(k => text.includes(k))
                && (below.textContent || '').trim().length >= MIN_CONSENT_LENGTH;
            });

            if (consentBelow) {
              console.warn('[SkillAudit SA-CSS-BDF-004] Dynamic backdrop-filter injected over consent text', {
                element: el,
                backdropFilter: bdFilter,
                consentElement: consentBelow
              });
            }
          }
        });
      });
    });
  });

  // Watch the entire document for new elements
  observer.observe(document.body, { childList: true, subtree: true });
  return observer;
}

// Initialize both detection phases
const staticFindings = detectBackdropFilterConsentOverlays();
const dynamicObserver = watchForDynamicBackdropFilter();

Why the hit-test approach is necessary (and not just inspecting parent elements)

A simpler approach might walk up the consent element's ancestor chain looking for backdrop-filter — but this only catches cases where the backdrop-filter element is an ancestor of the consent text. In the primary attack pattern (SA-CSS-BDF-001 through 004), the overlay is a sibling or cousin element positioned absolutely over the consent container — it is not an ancestor. The overlay is positioned within the same position: relative container. document.elementsFromPoint() performs actual hit-testing at a pixel coordinate, returning all elements in z-order at that point regardless of their DOM tree relationship to the consent element. This is the only reliable way to find elements that are visually above the consent text.

Defenses for MCP server developers

If you are building a legitimate MCP server with a genuine consent dialog, the following measures prevent the backdrop-filter attack pattern from being injected via prompt injection into your UI rendering layer:

How SkillAudit detects SA-CSS-BDF patterns

SkillAudit's consent UI security scanner includes full SA-CSS-BDF coverage:

HIGH SA-CSS-BDF-001: backdrop-filter: blur(≥4px) on element positioned over consent text — frosted-glass obscuration. Detected via elementsFromPoint() hit-test at consent element center + 2 quadrant sample points; reports overlay element's selector, blur radius, and computed blur/font-size ratio.
HIGH SA-CSS-BDF-002: backdrop-filter: brightness(≤0.1) on overlay — near-blackout of consent backdrop. Also flags brightness(0) applied via CSS variable chain where the resolved value is near-zero.
HIGH SA-CSS-BDF-003: backdrop-filter: contrast(≤0.2) on overlay — flat gray collapse of consent backdrop. Flagged alongside computed backdrop luminosity estimate.
HIGH SA-CSS-BDF-004: backdrop-filter value on element injected into consent-adjacent DOM after page load — dynamic injection via MutationObserver. Reports injection timing, triggering event (if detectable from event listener inspection), and cleanup timing.
MEDIUM SA-CSS-BDF-PERF: Any GPU compositing trigger (backdrop-filter, will-change, 3D transform) on element positioned over consent text — suspicious compositing signal regardless of specific visual effect. Escalated to HIGH if combined with low opacity on the overlying element.

The scanner runs in SkillAudit's headless Chromium environment with JavaScript execution enabled, and monitors DOM mutations throughout the simulated user flow (page load → hover over install button → click install button) to capture dynamic injection patterns that would be invisible to static analysis.

To run an audit on your MCP server or Claude skill, paste its GitHub URL into the SkillAudit scanner. SA-CSS-BDF findings are included in both the free public badge report and the full Pro report with remediation guidance.

Audit your MCP server for backdrop-filter consent attacks

SkillAudit's scanner checks for all SA-CSS-BDF patterns — static overlays, dynamic injection, CSS variable chains, and WebKit-prefixed variants. Paste a GitHub URL and get a graded report in 60 seconds.

Run free audit →