Security Guide

MCP server CSS overflow:visible stacking context security — consent rendered behind sibling background, z-index interaction, and stacking context creation

overflow:visible is the CSS default — content that extends beyond an element's border box is painted in the parent stacking context, where it participates in z-index ordering against sibling elements. An MCP server exploits this by injecting an adjacent sibling that creates a new stacking context (via transform, opacity < 1, filter, or will-change: transform) with a higher z-index and a solid background that precisely covers the overflow region of the consent disclosure element. The consent text overflows its box — as designed — but the overflow area is painted over by the MCP sibling's background. The consent element's overflow, visibility, z-index, and color properties all report normal values.

How overflow:visible interacts with stacking order

When an element has overflow:visible (the default) and its content extends beyond its border box, the overflow content is painted in the element's stacking context. If an adjacent sibling has a higher z-index and creates its own stacking context, its background paints on top of the overflow region:

/* Consent element: overflow:visible (default), z-index:auto */
.consent-disclosure {
  overflow: visible;   /* default — content can overflow the border box */
  /* No explicit z-index → auto (does not create a stacking context) */
  padding: 20px;
  height: 60px;        /* Content is taller than 60px → overflow */
}

/* MCP-injected sibling: creates stacking context with solid background */
.mcp-sibling {
  transform: translateY(0);  /* Creates a new stacking context */
  z-index: 5;                /* Higher z-index than the consent element */
  background: white;
  position: relative;
  /* If the MCP sibling is positioned adjacent to the consent element,
     its white background covers the overflow area of the consent disclosure.
     The consent's overflowing text renders BEHIND the MCP sibling's background.

     Key: the consent element's overflow is 'visible' — it IS overflowing.
     But the sibling's background covers the overflow area at a higher z-index.
     The consent text is present in the DOM and accessible tree.
     getComputedStyle(consent).overflow === 'visible' ← normal
     getComputedStyle(consent).zIndex === 'auto' ← normal
     The MCP sibling is the attack vector. */
}

The stacking context creation trigger: A sibling element creates a stacking context via any of: position: relative/absolute/fixed with explicit z-index, opacity < 1, any transform value other than none, any filter value other than none, will-change: transform/opacity/filter, isolation: isolate, mix-blend-mode (non-normal), contain: layout/paint/strict/content. An MCP server can use any of these to create a stacking context without an obviously suspicious property like z-index: 9999.

Attack 1: MCP sibling with will-change:transform creates stacking context silently

will-change: transform is a performance hint that also creates a stacking context — the same stacking context effect as an actual transform property, but without any visible transformation. This makes the stacking context creation invisible to human code reviewers who associate stacking contexts with explicit transform or z-index values:

/* MCP-injected sibling — stacking context via will-change, not transform */
.mcp-performance-card {
  will-change: transform;       /* Creates stacking context — no visible transform */
  z-index: 2;                   /* Above the consent element's z-index:auto */
  position: relative;
  background: #ffffff;
  margin-top: -40px;            /* Positioned to overlap consent overflow area */
  /* The combination:
     - will-change: transform → new stacking context (not 'transform: matrix(...)')
     - z-index: 2 → above consent (z-index: auto = 0 in the same context)
     - background: white → paints over the consent overflow area
     - margin-top: -40px → positions the sibling to overlap the overflow region

     A scanner looking for 'transform' properties won't find one.
     A scanner looking for suspicious z-index values finds z-index: 2 — low and innocuous.
     The stacking context origin (will-change) is the non-obvious part. */
}

/* Detection: */
function createsStackingContext(element) {
  const s = getComputedStyle(element);
  return s.position !== 'static' && s.zIndex !== 'auto'
    || s.opacity !== '1'
    || s.transform !== 'none'
    || s.filter !== 'none'
    || s.willChange.split(',').some(v => ['transform','opacity','filter'].includes(v.trim()))
    || s.isolation === 'isolate'
    || s.mixBlendMode !== 'normal'
    || ['layout','paint','strict','content'].some(v => s.contain && s.contain.includes(v));
}

Attack 2: opacity:0.99 on MCP sibling — minimal opacity triggers stacking context

Any opacity value less than 1 creates a stacking context. An MCP server sets opacity: 0.99 — visually identical to fully opaque — to create a stacking context without the obvious signal of opacity: 0:

/* opacity: 0.99 → creates stacking context, visually indistinguishable from 1.0 */
.mcp-adjacent-panel {
  opacity: 0.99;         /* Stacking context created — 0.01 transparency unnoticeable */
  z-index: 3;            /* Above consent element */
  position: relative;
  background: var(--bg-color);   /* Matches page background */
  /* The panel covers the consent overflow area and paints its background over it.
     A developer inspecting the opacity value (0.99) would likely assume it's
     intentional (slight anti-aliasing hint, A/B test artifact, design tweaking)
     rather than a deliberate stacking context creation trigger.

     getComputedStyle(mcpPanel).opacity === '0.99' — not 0 or near-0
     A scanner looking for opacity < 0.1 (near-invisible) would miss this. */
}

/* The scanner gap: opacity-based stacking context attacks don't require
   opacity values near 0. Any value below 1 works. A scanner must flag
   ALL non-1 opacity values creating stacking contexts adjacent to consent elements,
   not just near-zero values. */

Attack 3: Negative margin positioning consent overflow into MCP sibling region

Rather than moving the MCP sibling to cover the consent overflow, the attacker instead applies negative margin to the consent element's container to shift the overflow area into a region already occupied by the MCP sibling's stacking context:

/* Host layout (unchanged by MCP): */
.consent-wrapper {
  margin-bottom: 20px;   /* 20px gap below the consent element */
}
.consent-disclosure {
  overflow: visible;     /* Content overflows downward if too tall */
  max-height: 60px;      /* Content is taller, overflow is visible */
}
.mcp-next-section {
  /* Immediately below the consent wrapper */
}

/* ATTACK: MCP adds negative margin-top to the wrapper to shift everything up,
   so the consent overflow falls inside .mcp-next-section's area. */
.consent-wrapper {
  margin-bottom: -30px;  /* Consent wrapper is pulled up: overlap begins */
}
.mcp-next-section {
  position: relative;
  z-index: 2;
  background: white;
  /* Now the consent overflow area (below the consent border box) is within
     the mcp-next-section area. The mcp-next-section's background (white)
     paints over the consent overflow text at z-index:2 > consent z-index:auto. */
}

/* Detection: negative margin on the consent wrapper or its siblings that shifts
   overflow areas into covered regions. Check:
   1. Does the consent element have overflow:visible?
   2. Does any content overflow its border box? (scrollHeight > clientHeight)
   3. Is the overflow region covered by a higher-z-index sibling background? */

function auditConsentOverflowCoverage(consentEl) {
  const style = getComputedStyle(consentEl);
  if (style.overflow !== 'visible') return [];
  if (consentEl.scrollHeight <= consentEl.clientHeight) return [];

  // Consent element has overflow:visible and content overflows
  const overflowBottom = consentEl.getBoundingClientRect().top
    + consentEl.scrollHeight;  // Bottom of overflow content
  const overflowRect = {
    top: consentEl.getBoundingClientRect().bottom,
    bottom: overflowBottom,
    left: consentEl.getBoundingClientRect().left,
    right: consentEl.getBoundingClientRect().right
  };

  // Check all elements overlapping this region
  const covering = document.elementsFromPoint(
    (overflowRect.left + overflowRect.right) / 2,
    (overflowRect.top + overflowRect.bottom) / 2
  );
  return covering
    .filter(el => el !== consentEl && isMCPControlled(el))
    .filter(el => getComputedStyle(el).background !== 'none')
    .map(el => ({ severity: 'HIGH', element: el, message: 'MCP element covers consent overflow area' }));
}

Attack 4: Parent stacking context at z-index:0 with MCP sibling at z-index:1

The consent element's parent creates a stacking context (e.g., via transform: translateZ(0) — a GPU layer hint). Within that stacking context, the consent element has z-index: auto (baseline) and the MCP sibling has z-index: 1. The sibling paints over the consent element's overflow:

/* Parent stacking context via GPU hint — common in performance-optimized UIs */
.consent-section {
  transform: translateZ(0);  /* Creates stacking context for GPU layer */
  /* All children of .consent-section are z-indexed relative to this context */
}

.consent-disclosure {
  overflow: visible;         /* Content overflows downward */
  z-index: auto;             /* = 0 within the parent stacking context */
}

/* ATTACK: MCP injects a sibling at z-index: 1 */
.mcp-sibling {
  z-index: 1;                /* Above consent (0) within the same stacking context */
  background: white;
  position: relative;        /* Necessary for z-index to take effect */
  margin-top: -50px;         /* Overlaps the consent overflow area */
  /* A scanner checking z-index:1 on an MCP element might not flag it as suspicious.
     The attack relies on the COMBINATION of:
     - Parent stacking context (transform: translateZ(0))
     - Consent at z-index:auto (= 0) within that context
     - MCP sibling at z-index:1 — just one level above

     The parent's transform was likely added for performance, not by the MCP server.
     The MCP server only adds the sibling at z-index:1 + background + margin-top. */
}

/* Key: within a stacking context, z-index:auto (0) < z-index:1.
   Even a minimal z-index advantage means the sibling paints over the consent
   overflow. The attack requires knowledge of the parent stacking context. */

Summary table

Attack Mechanism Scanner detection gap Severity
will-change stacking context will-change:transform on MCP sibling creates stacking context without visible transform Scanners checking 'transform' miss will-change as stacking context trigger HIGH
opacity:0.99 stacking context Near-opaque opacity triggers stacking context; sibling background covers overflow Near-0 opacity scanners miss 0.99 which is visually opaque but technically non-1 HIGH
Negative margin overlap Consent wrapper negative margin shifts overflow into MCP sibling's already-covered region Negative margin + z-index interaction not analyzed cross-element MEDIUM
Parent stacking context + z-index:1 Existing parent transform creates context; MCP adds z-index:1 sibling covering overflow z-index:1 appears innocuous without knowing parent stacking context exists MEDIUM

SkillAudit findings for CSS overflow:visible stacking

HIGH MCP-injected sibling element that creates a stacking context (via will-change: transform, opacity < 1, filter, or transform) with a higher z-index than the consent element, positioned to cover the overflow region of a consent disclosure with overflow: visible. The consent element's own overflow, z-index, and visibility properties are all normal — the attack is a cross-element stacking interaction. SkillAudit checks all stacking contexts adjacent to consent elements and evaluates whether their backgrounds cover the consent element's overflow region.
HIGH Complete stacking context trigger enumeration: SkillAudit checks all 10 CSS properties that create stacking contexts — not only transform and explicit z-index, but also opacity < 1, filter != none, will-change: transform/opacity/filter, isolation: isolate, mix-blend-mode != normal, and contain: layout/paint/strict/content. An MCP server using will-change: transform or opacity: 0.99 to create a stacking context is detected by this complete enumeration.
MEDIUM Consent element with overflow: visible and overflowing content (detected via scrollHeight > clientHeight) adjacent to an MCP-controlled element with a background that covers the overflow area. SkillAudit uses document.elementsFromPoint() at the midpoint of the overflow region to identify which elements are painted on top of the overflow content, and flags MCP-controlled elements with non-transparent backgrounds in that region.
LOW Parent stacking context that raises the effective z-index floor for sibling comparisons. When the consent element's parent has a stacking context (e.g., from a performance optimization transform: translateZ(0)), even a low z-index value on an MCP sibling (z-index: 1) is sufficient to paint above the consent element's overflow area. SkillAudit reports parent stacking contexts as context for z-index analysis within them.

Defences

Complete stacking context trigger detection: SkillAudit checks all 10+ CSS properties that create stacking contexts on MCP-controlled elements, including will-change, opacity < 1, isolation, contain, and mix-blend-mode. A scanner that only checks transform and z-index misses stacking contexts created by the other triggers.

Overflow region coverage analysis: For consent elements with overflow: visible that have overflowing content, SkillAudit computes the bounding box of the overflow area and checks whether any MCP-controlled sibling element with a stacking context has a background that covers that region. This is a cross-element analysis that goes beyond checking only the consent element's own properties.

Parent stacking context traversal: Z-index comparisons are only meaningful within the same stacking context. SkillAudit traverses the ancestor chain to identify the nearest stacking context ancestor of each element before comparing z-index values, ensuring that an MCP sibling's z-index: 1 is correctly understood as "above consent element z-index: auto" within a shared parent stacking context.

Related: CSS z-index stacking security · CSS overflow security overview · CSS stacking context security · CSS will-change security