Security reference · CSS injection · contain · Consent manipulation

MCP server CSS contain consent security — paint containment consent clipping attack

CSS contain is a performance optimization property that tells the browser an element's subtree is independent from the rest of the document. Each containment type — paint, size, layout, and style — creates a side effect that can be weaponised to hide consent text. The most dangerous: contain: paint clips overflowing content even when overflow: visible is set, creating a discrepancy between the audited property value and the actual rendering behavior that fools every overflow-checking consent auditor.

contain attack surface overview

Attack ID Containment value Key side effect abused Audit blind spot
SA-CSS-CNT-001 contain: paint Forces overflow clipping independent of overflow computed value Auditor checks overflow: visible — concludes content is not clipped; it is
SA-CSS-CNT-002 contain: size Decouples element dimensions from content — enables 0×0 consent containers Auditor checks hasChildNodes() or textContent — consent text is present; the container is zero-size
SA-CSS-CNT-003 contain: layout Isolates stacking context — absolutely-positioned cover divs cannot escape the containment boundary Auditor checks consent element's own z-index — it is normal; cover div's z-index:999 inside the container occludes it
SA-CSS-CNT-004 contain: strict All four containment types simultaneously — size, layout, paint, style are all active Any single-property audit (overflow, dimensions, z-index, custom properties) passes — the full combination is required to detect the bypass

Performance optimization masquerade: CSS contain is a widely-recommended performance technique for large DOM trees, virtual scroll lists, and widget isolation. Its presence on a UI element is not inherently suspicious. A code review seeing contain: paint on the consent container would likely assume it is a render-performance optimization for the install dialog. The attack surface is the side effects of containment — particularly overflow clipping behavior and stacking context isolation — not the property's stated purpose.

Background: how CSS contain works

The CSS Containment specification defines four containment types that can be applied independently or combined. size containment: the element's size is not influenced by its children (the browser can compute the element's size without looking at children — enabling display virtualization). layout containment: the element's internal layout is isolated — the element becomes an independent formatting context, a new stacking context, and a containing block for absolute/fixed positioned descendants. paint containment: all of the above, plus the element's painting area is clipped to its border box (any overflowing content is not painted). style containment: scopes CSS counters and custom property inheritance to the element's subtree.

The shorthand keywords are: content (= layout paint style) and strict (= size layout paint style). The paint and strict values are the most dangerous for consent bypass because they force overflow clipping regardless of the overflow property setting.

Attack 1: contain: paint clips consent text despite overflow: visible (SA-CSS-CNT-001)

CSS contain: paint creates an independent painting context for the element. One of the effects of this is that the element's content is clipped to the element's border box — exactly as if overflow: hidden were set. However, the overflow computed value remains whatever was specified (e.g., visible). An auditor checking getComputedStyle(el).overflow finds 'visible' and concludes the content is not clipped. It is. JavaScript then reduces the container height to 1px after page load, making the consent text invisible. The audit sees overflow: visible and a 1px container and does not flag it — because with overflow: visible, a 1px container should not clip content. But contain: paint overrides that rule.

/* Attack: contain:paint clips overflow:visible — the audit lie */
.consent-container {
  contain: paint;       /* ← forces overflow clipping at border box */
  overflow: visible;    /* ← what getComputedStyle returns: 'visible' */
  /* The computed overflow value is 'visible' — auditor sees no clip.
     But contain:paint forces paint clipping at the border box
     independently of the overflow computed value.
     Content that extends outside the border box is NOT painted.
     Auditors checking overflow: PASS (visible)
     Actual rendering behavior: CLIPPED */

  height: 200px;        /* initial height — consent is visible */
  transition: height 0s; /* no transition — change is instant */
}

/* JS reduces height to 1px after a delay, after the page appears to load */
document.addEventListener('DOMContentLoaded', () => {
  const consentContainer = document.querySelector('.consent-container');

  // Technique 1: simple setTimeout
  setTimeout(() => {
    consentContainer.style.height = '1px';
    // With overflow:visible + no contain: 1px height, text would overflow and remain visible
    // With contain:paint: 1px height, text is clipped at 1px — INVISIBLE
  }, 100); // 100ms — page appears loaded, user has not yet read consent

  // Technique 2: IntersectionObserver — wait until install button is in view
  const installBtn = document.querySelector('.install-button');
  const obs = new IntersectionObserver((entries) => {
    if (entries[0].isIntersecting) {
      consentContainer.style.height = '1px';
      obs.disconnect();
    }
  });
  obs.observe(installBtn);

  // Technique 3: requestAnimationFrame chain — defers past initial paint
  requestAnimationFrame(() => {
    requestAnimationFrame(() => {
      consentContainer.style.height = '1px';
      // The double-rAF ensures this fires after the initial paint
      // where the consent was visible, satisfying screenshot-based audits
    });
  });
});

/* Why this bypasses overflow audits:

   Standard audit check:
   const overflow = getComputedStyle(consentContainer).overflow; // → 'visible'
   const overflowY = getComputedStyle(consentContainer).overflowY; // → 'visible'
   if (overflow === 'hidden' || overflow === 'clip') FLAG();
   // Result: no flag — overflow is 'visible'

   But the ACTUAL rendering behavior:
   contain:paint → equivalent to overflow:hidden for paint purposes
   height:1px + contain:paint → 1px of content visible → ALL consent hidden

   The audit checks the wrong property. contain:paint creates a paint clipping
   context that is not reflected in the overflow computed value. */

/* What the DOM audit finds on the consent text node:
   textContent: "By installing this server you authorize access..." → correct ✓
   hasChildNodes(): true → correct ✓
   color: #1a1a1a → legible ✓
   font-size: 14px → readable ✓
   visibility: visible → visible ✓
   overflow: visible → ← MISLEADING but technically correct
   height of container: 1px → container is 1px
   But without checking contain, the auditor concludes overflow:visible + 1px = text overflows and is visible.
   It is not. */

SA-CSS-CNT-001 (Critical). This is the only CSS consent bypass where a correct property value (overflow: visible) actively misleads an auditor into concluding the content is not clipped, when in fact it is. Detection requires checking contain in addition to overflow. If contain includes paint (either as a standalone value, as part of content, or as part of strict), treat the element as having overflow: hidden for consent audit purposes, regardless of the overflow computed value.

/* Detection: contain:paint creates hidden overflow despite overflow:visible */
function detectContainPaintClip() {
  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 cs = getComputedStyle(el);
    const contain = cs.contain || '';
    const overflow = cs.overflow;
    const overflowY = cs.overflowY;
    const height = el.getBoundingClientRect().height;
    const scrollHeight = el.scrollHeight;

    // Check if contain includes paint (directly or via 'content' or 'strict')
    const hasPaintContain = contain.includes('paint') ||
                            contain.includes('content') ||
                            contain.includes('strict');

    if (hasPaintContain) {
      // overflow:visible + contain:paint = content IS clipped despite overflow:visible
      if (overflow === 'visible' || overflowY === 'visible') {
        findings.push({
          vuln: 'SA-CSS-CNT-001-misleading-overflow',
          severity: 'CRITICAL',
          element: el,
          detail: `contain:${contain} forces paint clipping; overflow:${overflow} shows 'visible' ` +
                  `but content IS clipped at border box; standard overflow audit PASSES incorrectly`
        });
      }

      // Check if JS has reduced height to near-zero
      if (height < 10 && scrollHeight > 20) {
        findings.push({
          vuln: 'SA-CSS-CNT-001-height-collapse',
          severity: 'CRITICAL',
          element: el,
          detail: `contain:paint + height:${Math.round(height)}px collapses consent container; ` +
                  `${Math.round(scrollHeight)}px of consent text invisible; overflow:${overflow} misleads audit`
        });
      }
    }
  });

  return findings;
}

// MutationObserver: catch JS height manipulation after page load
function watchContainPaintHeightCollapse(consentEl) {
  const cs = getComputedStyle(consentEl);
  const contain = cs.contain || '';
  const hasPaintContain = contain.includes('paint') ||
                          contain.includes('content') ||
                          contain.includes('strict');
  if (!hasPaintContain) return null;

  const observer = new MutationObserver(() => {
    const newHeight = consentEl.getBoundingClientRect().height;
    if (newHeight < 10) {
      console.error('[SkillAudit] SA-CSS-CNT-001: contain:paint element height collapsed to',
                    newHeight, 'px — consent text invisible despite overflow:visible');
      // Disable install button
      document.querySelectorAll('.install-button, [data-install]')
              .forEach(btn => btn.disabled = true);
    }
  });
  observer.observe(consentEl, { attributes: true, attributeFilter: ['style', 'class'] });
  return observer;
}

Attack 2: contain: size creates zero-size consent containers that pass DOM checks (SA-CSS-CNT-002)

contain: size tells the browser that the element's size does not depend on its children. The browser is therefore free to compute the element's dimensions without looking at its content. Combined with explicit width: 0; height: 0 or simply no size declaration (some browsers default contained elements to zero intrinsic size), the consent container is zero-size. The consent text is in the DOM — hasChildNodes() returns true, textContent returns the full consent string, all child nodes are present. But the container occupies zero visual space. Since contain: paint is implied when the container has zero dimensions, the content is also clipped. An auditor using DOM presence checks — the most common form of automated consent verification — finds the consent text in the DOM and reports no issues.

/* Attack: contain:size allows a zero-size consent container with full DOM content */
.consent-container {
  contain: size;        /* ← size is independent of content */
  width: 0;            /* explicit zero width */
  height: 0;           /* explicit zero height */
  /* With contain:size, the browser accepts these dimensions without
     trying to expand the element to fit its content.
     Without contain:size, width:0 height:0 would be overridden by
     the intrinsic size of the text content.
     With contain:size, the browser treats the element as if it
     has no content for sizing purposes — 0x0 is valid. */
  overflow: hidden;    /* or overflow:visible — with contain:size and 0x0,
                          either value results in no visible content */
}

/* The consent text — fully present in the DOM */
.consent-text {
  /* This element has full, correct consent text as textContent */
  /* color, font-size, visibility are all correct */
  /* The TEXT is here — it is just rendered inside a 0x0 container */
}

/* What DOM-based consent auditors see:
   consentContainer.hasChildNodes()              → true ✓
   consentContainer.textContent.length           → 287 characters ✓
   consentContainer.querySelector('.consent-text') → element found ✓
   consentContainer.textContent.includes('authorize') → true ✓
   consentText.textContent                       → full consent string ✓

   What the user sees: nothing — 0x0 container, no visible content */

/* Variant using intrinsic size without explicit 0x0 */
.consent-container-v2 {
  contain: size;
  /* No width/height specified.
     Without contain:size, the browser would expand the element to fit content.
     With contain:size, the browser uses 0x0 as the intrinsic size for
     elements with no explicit size declaration.
     Result: element collapses to 0x0 automatically. */
}

/* contain:size is commonly used for CSS Layout Worklets (Houdini) and
   for list virtualization where item sizes are known in advance.
   Its presence on a consent container looks like a performance micro-optimization
   to a developer unfamiliar with CSS containment semantics. */

SA-CSS-CNT-002 (Critical). DOM presence checks are the most common form of automated consent verification. An auditor that checks textContent, hasChildNodes(), or querySelector for the consent element will find the content present and report no issues. contain: size makes DOM presence an unreliable consent verification method. Detection requires checking the rendered dimensions of the consent container (getBoundingClientRect().width and .height) in addition to DOM presence. A consent container with zero or near-zero rendered dimensions is a critical finding regardless of DOM content.

/* Detection: contain:size enables zero-size consent containers */
function detectContainSizeZeroBox() {
  const CONSENT_KEYWORDS = ['authorize', 'grant', 'access', 'permission',
                            'agree', 'terms', 'third-party'];
  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 cs = getComputedStyle(el);
    const contain = cs.contain || '';
    const rect = el.getBoundingClientRect();
    const renderedArea = rect.width * rect.height;

    const hasSizeContain = contain.includes('size') || contain.includes('strict');

    if (hasSizeContain) {
      if (renderedArea === 0) {
        findings.push({
          vuln: 'SA-CSS-CNT-002',
          severity: 'CRITICAL',
          element: el,
          detail: `contain:${contain} with zero rendered dimensions (${rect.width}×${rect.height}px); ` +
                  `consent text (${el.textContent.trim().length} chars) present in DOM but invisible; ` +
                  `DOM presence checks return true — visually zero area`
        });
      } else if (renderedArea < 100) {
        // Near-zero — e.g., 10×1px
        findings.push({
          vuln: 'SA-CSS-CNT-002-near-zero',
          severity: 'HIGH',
          element: el,
          detail: `contain:${contain} with near-zero rendered dimensions (${rect.width}×${rect.height}px); ` +
                  `consent text effectively invisible`
        });
      }
    }

    // Also flag any consent element with zero rendered area regardless of contain
    // (contain:size makes the zero-size possible despite content, but zero-area
    // consent should be flagged unconditionally)
    if (renderedArea === 0 && el.textContent.trim().length > 30) {
      findings.push({
        vuln: 'SA-CSS-CNT-002-zero-area',
        severity: 'CRITICAL',
        element: el,
        detail: `Consent element has zero rendered area (${rect.width}×${rect.height}px) ` +
                `despite ${el.textContent.trim().length} characters of text content; ` +
                `contain:${contain || 'none'}`
      });
    }
  });

  return findings;
}

// containment-aware reachability check
function isConsentElementVisible(el) {
  const cs = getComputedStyle(el);
  const contain = cs.contain || '';
  const rect = el.getBoundingClientRect();

  // Standard checks
  if (cs.visibility === 'hidden') return false;
  if (cs.display === 'none') return false;
  if (parseFloat(cs.opacity) === 0) return false;

  // Containment-aware check: contain:size may make element 0×0
  if (rect.width === 0 || rect.height === 0) return false;

  // contain:paint clips overflow regardless of overflow value
  const hasPaintContain = contain.includes('paint') ||
                          contain.includes('content') ||
                          contain.includes('strict');
  if (hasPaintContain && rect.height < 10) return false;

  return true;
}

Attack 3: contain: layout isolates cover-div stacking context to occlude consent (SA-CSS-CNT-003)

contain: layout creates an independent formatting context that includes a new stacking context. Absolutely-positioned children of a contain: layout element cannot be positioned outside its boundaries — they are contained. An attacker places a white cover-div absolutely inside the consent container with position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: white; z-index: 999. This cover div sits on top of the consent text, making it invisible. Normally, auditors checking whether anything is positioned on top of the consent element would examine the global z-index stacking context. But contain: layout isolates the stacking context to within the consent container — the cover div's z-index: 999 is evaluated only within the container's local stacking context, not the global one. The cover div is invisible to auditors checking for z-index conflicts in the global stacking context.

/* Attack: contain:layout isolates stacking context — cover div occludes consent */

/* HTML structure */
/*
  <div class="consent-container">
    <p class="consent-text">By installing this server you authorize...</p>
    <div class="cover-div"></div>  <!-- occludes the consent text -->
  </div>
*/

.consent-container {
  contain: layout;      /* ← creates isolated stacking context */
  position: relative;   /* establishes positioning context for cover-div */
  /* Normal visual appearance from outside: a block-level element */
}

.consent-text {
  /* Normal styles — all correct */
  font-size: 14px;
  color: #1a1a1a;
  line-height: 1.6;
  /* z-index: auto (not set) */
}

.cover-div {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: white;    /* or background: var(--bg) to match the page background */
  z-index: 999;         /* within the isolated stacking context of .consent-container */
  /* This cover div completely occludes .consent-text */
  /* Without contain:layout, the cover-div's z-index participates in the
     global stacking context and could be detected by auditors looking for
     high-z-index overlays on consent elements.
     With contain:layout, the cover-div's z-index is evaluated only within
     .consent-container's local stacking context — it is 'above' .consent-text
     (z-index:auto < z-index:999 within the same stacking context) */

  /* An audit checking document.elementsFromPoint(x, y) at the consent
     text position would return:
     [cover-div, consent-container, ...]
     The cover-div is detected.
     But an audit checking for 'suspicious z-index' globally:
     max z-index in global stacking context → does not include 999
     (because contain:layout scopes z-index to local stacking context)
     Simple global z-index audits: PASS (no high z-index elements found globally) */
}

/* Variant: cover-div has background:inherit or background matching the install dialog */
.cover-div-subtle {
  position: absolute;
  inset: 0;
  /* background: var(--install-dialog-bg); */
  /* The cover appears to be part of the dialog styling, not an overlay */
  z-index: 1; /* even z-index:1 suffices within the local stacking context */
}

/* contain:layout is commonly used for widget isolation in design systems.
   The cover-div pattern mimics common CSS reset or stacking-context-fix patterns
   that developers use when dealing with z-index issues. A code reviewer is unlikely
   to identify .cover-div as malicious — it looks like a visual fix. */

Cover div detectability: document.elementsFromPoint(x, y) at the consent text's rendered coordinates WILL detect the cover div — it is the topmost element at that position. The attack is specifically against auditors using CSS property inspection rather than positional hit-testing. SkillAudit uses both: it inspects computed styles AND calls document.elementsFromPoint at the center of each consent element to verify that the consent element (or its text nodes) is the topmost element, not a cover element. contain: layout defeats z-index global analysis but does not defeat positional hit-testing.

/* Detection: contain:layout + absolutely-positioned cover element over consent */
function detectContainLayoutCoverDiv() {
  const CONSENT_KEYWORDS = ['authorize', 'grant', 'access', 'permission',
                            'agree', 'terms', 'third-party'];
  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 cs = getComputedStyle(el);
    const contain = cs.contain || '';
    const hasLayoutContain = contain.includes('layout') ||
                             contain.includes('content') ||
                             contain.includes('strict');

    if (hasLayoutContain) {
      // Check for absolutely-positioned children that cover the element
      const absChildren = [];
      Array.from(el.children).forEach(child => {
        const childCs = getComputedStyle(child);
        if (childCs.position === 'absolute' || childCs.position === 'fixed') {
          const childRect = child.getBoundingClientRect();
          const elRect = el.getBoundingClientRect();
          // Check if child covers most of the parent
          const coverageX = Math.min(childRect.right, elRect.right) -
                            Math.max(childRect.left, elRect.left);
          const coverageY = Math.min(childRect.bottom, elRect.bottom) -
                            Math.max(childRect.top, elRect.top);
          const parentArea = elRect.width * elRect.height;
          const coverArea = Math.max(0, coverageX) * Math.max(0, coverageY);
          if (parentArea > 0 && (coverArea / parentArea) > 0.7) {
            absChildren.push({ child, coverage: coverArea / parentArea });
          }
        }
      });

      if (absChildren.length > 0) {
        findings.push({
          vuln: 'SA-CSS-CNT-003',
          severity: 'HIGH',
          element: el,
          detail: `contain:${contain} creates isolated stacking context; ` +
                  `${absChildren.length} absolutely-positioned child(ren) cover ` +
                  `${Math.round(absChildren[0].coverage * 100)}% of consent element area; ` +
                  `cover-div z-index scoped to local stacking context bypasses global z-index audit`
        });
      }
    }

    // Hit-test: verify consent text is topmost at its rendered position
    const rect = el.getBoundingClientRect();
    if (rect.width > 0 && rect.height > 0) {
      const centerX = rect.left + rect.width / 2;
      const centerY = rect.top + rect.height / 2;
      const topmostEl = document.elementFromPoint(centerX, centerY);
      if (topmostEl && !el.contains(topmostEl) && topmostEl !== el) {
        // Something else is on top of the consent element at its center point
        findings.push({
          vuln: 'SA-CSS-CNT-003-hit-test',
          severity: 'CRITICAL',
          element: el,
          topmostElement: topmostEl,
          detail: `Consent element is not the topmost element at its center coordinates; ` +
                  `${topmostEl.tagName}.${topmostEl.className} is on top; ` +
                  `contain:${contain} on consent container`
        });
      }
    }
  });

  return findings;
}

Attack 4: contain: strict combines all four containment types simultaneously (SA-CSS-CNT-004)

contain: strict is equivalent to contain: size layout paint style. All four containment types are active simultaneously. For the consent container: size containment decouples the container's dimensions from its text content (enabling 0×0 or 1px height); layout containment isolates the formatting context and stacking context (cover divs cannot escape, and the stacking context is isolated from global audit); paint containment forces overflow clipping even when overflow: visible is set; style containment scopes CSS custom properties to the subtree (consent styles cannot be inspected from the parent's computed property values). The combined effect is that no single property check detects the attack — an auditor must inspect and correctly interpret all four containment effects together.

/* Attack: contain:strict applies all four containment types — hardest to detect */

/* The consent container */
.consent-container {
  contain: strict;
  /* Equivalent to:
     contain: size layout paint style
     All four effects active simultaneously:

     SIZE: container dimensions are independent of content.
           Set height:1px → content clipped regardless of text length.
           DOM audits find textContent, but it is inside a 1px box.

     LAYOUT: independent formatting context + new stacking context.
             Absolutely-positioned children cannot escape.
             Cover divs are z-index:999 locally but invisible to global audit.
             Also: the consent container is a containing block for any fixed
             descendants (fixed-position elements inside are relative to this,
             not the viewport — they cannot overlay the install button).

     PAINT: overflow clipping at border box.
            overflow:visible in computed style, but content IS clipped.
            The standard audit check fails.

     STYLE: custom property inheritance scoped to subtree.
            --consent-color, --consent-font-size etc. defined on the container
            do not leak out to parent. Auditors checking parent computed
            properties for consent styling cannot see the values.
            This disrupts theming-based consent detection. */

  /* Typical dimension with strict: */
  height: 1px;           /* size containment: accepted despite content */
  width: 100%;           /* full width (size containment: accepted) */
  overflow: visible;     /* paint containment: content is clipped anyway */

  /* Custom properties scoped by style containment: */
  --consent-color: transparent; /* style containment: does not leak to parent */
}

/* Targeting the consent text with style-contained custom properties */
.consent-text {
  /* If color uses a custom property defined inside the contain:strict element,
     the custom property does not inherit from the parent cascade.
     An auditor checking the parent's --consent-color would get a different value. */
  color: var(--consent-color, #1a1a1a);
  /* Inside the strict-contained subtree, --consent-color is 'transparent'
     from the container's definition.
     The parent's computed --consent-color (not set) defaults to initial → #1a1a1a.
     Auditor checking parent: color resolves to legible #1a1a1a ← WRONG
     Actual rendered color inside subtree: transparent ← content invisible */
}

/* Combining strict + cover div + custom property attack */
/*
  contain:strict scopes the stacking context (layout containment effect).
  An absolute cover div inside the container can only be detected via hit-testing.
  contain:strict also clips overflow (paint containment effect), so a 1px container
  can simultaneously use a cover div and overflow clipping — belt and suspenders.

  The most resilient attack form:
  1. height:1px — collapses visible area
  2. overflow:visible — misleads overflow auditors
  3. contain:strict — enforces the clip despite overflow:visible
  4. contain:style scopes custom property values to transparent
  5. cover div with z-index:1 inside isolated stacking context — occludes text

  Any single-property audit passes. All five properties must be evaluated together. */

SA-CSS-CNT-004 (Critical). contain: strict is a CSS performance optimization designed for content that is fully size-independent and layout-independent — virtual scroll items, complex widgets. Its use on an install dialog's consent container has no performance justification (consent dialogs are small, static, single-instance elements). SkillAudit flags contain: strict or contain: size layout paint on consent-bearing containers as a critical finding regardless of other property values, because the property combination creates a multi-vector bypass that requires complete containment-aware analysis to detect.

/* Detection: contain:strict unified detection for all four containment attacks */
function detectContainStrict() {
  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 cs = getComputedStyle(el);
    const contain = cs.contain || '';

    if (!contain || contain === 'none' || contain === 'normal') return;

    const hasPaint = contain.includes('paint') || contain.includes('content') ||
                     contain.includes('strict');
    const hasSize = contain.includes('size') || contain.includes('strict');
    const hasLayout = contain.includes('layout') || contain.includes('content') ||
                      contain.includes('strict');
    const hasStyle = contain.includes('style') || contain.includes('content') ||
                     contain.includes('strict');

    const rect = el.getBoundingClientRect();
    const severity = (contain.includes('strict') ||
                     (hasPaint && hasSize)) ? 'CRITICAL' : 'HIGH';

    // Size containment: check rendered dimensions
    if (hasSize && rect.width * rect.height < 100) {
      findings.push({
        vuln: 'SA-CSS-CNT-002',
        severity: 'CRITICAL',
        element: el,
        detail: `contain includes size; rendered area ${rect.width}×${rect.height}px; ` +
                `consent text invisible despite DOM presence`
      });
    }

    // Paint containment: check overflow:visible discrepancy
    if (hasPaint && (cs.overflow === 'visible' || cs.overflowY === 'visible')) {
      const scrollHeight = el.scrollHeight;
      const clientHeight = el.clientHeight;
      if (clientHeight < 10 || scrollHeight > clientHeight + 5) {
        findings.push({
          vuln: 'SA-CSS-CNT-001',
          severity: 'CRITICAL',
          element: el,
          detail: `contain includes paint; overflow:${cs.overflow} misleads audit; ` +
                  `content IS clipped at border box; ` +
                  `scrollHeight:${scrollHeight}px clientHeight:${clientHeight}px`
        });
      }
    }

    // Layout containment: check for cover divs
    if (hasLayout && rect.width > 0 && rect.height > 0) {
      const centerX = rect.left + rect.width / 2;
      const centerY = rect.top + rect.height / 2;
      const topmostEl = document.elementFromPoint(centerX, centerY);
      if (topmostEl && !el.contains(topmostEl) && topmostEl !== el) {
        findings.push({
          vuln: 'SA-CSS-CNT-003',
          severity: 'CRITICAL',
          element: el,
          topmostElement: topmostEl,
          detail: `contain includes layout (isolated stacking context); ` +
                  `${topmostEl.tagName} occludes consent at center coordinates; ` +
                  `global z-index audit bypassed by stacking context isolation`
        });
      }
    }

    // Style containment: check custom property scope
    if (hasStyle) {
      // Check if consent text color resolves to transparent or invisible
      // via a custom property scoped inside the contain:style boundary
      const consentTextEl = el.querySelector('p, span, div') || el;
      const resolvedColor = getComputedStyle(consentTextEl).color;
      // Parse rgba(r,g,b,a) — check alpha channel
      const alphaMatch = resolvedColor.match(/rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*([\d.]+)/);
      if (alphaMatch && parseFloat(alphaMatch[1]) < 0.1) {
        findings.push({
          vuln: 'SA-CSS-CNT-004-style',
          severity: 'CRITICAL',
          element: el,
          detail: `contain includes style (custom property scope); ` +
                  `consent text color resolves to ${resolvedColor} (near-transparent); ` +
                  `--color custom property inside scope differs from parent cascade`
        });
      }
    }

    // Strict containment: flag the whole combination
    if (contain.includes('strict') ||
        (hasPaint && hasSize && hasLayout)) {
      findings.push({
        vuln: 'SA-CSS-CNT-004',
        severity: 'CRITICAL',
        element: el,
        detail: `contain:${contain} on consent container combines size+layout+paint${hasStyle ? '+style' : ''}; ` +
                `multi-vector bypass — all four containment attacks active simultaneously; ` +
                `no single-property audit is sufficient for detection`
      });
    }
  });

  return findings;
}

// Consolidated contain audit — runs all four patterns
function runContainConsentAudit() {
  const allFindings = [
    ...detectContainPaintClip(),
    ...detectContainSizeZeroBox(),
    ...detectContainLayoutCoverDiv(),
    ...detectContainStrict()
  ];
  // Deduplicate by element + vuln
  const seen = new Set();
  return allFindings.filter(f => {
    const key = (f.element ? f.element.tagName + f.element.className : '') + f.vuln;
    if (seen.has(key)) return false;
    seen.add(key);
    return true;
  });
}

SkillAudit detection

CRITICAL SA-CSS-CNT-001: contain: paint forces overflow clipping on the consent container regardless of overflow: visible — standard overflow audits report no clipping but content is clipped. Combined with JS height collapse to 1px, consent becomes invisible. SkillAudit checks contain on consent containers and applies paint-containment-aware overflow rules.
CRITICAL SA-CSS-CNT-002: contain: size decouples the consent container's dimensions from its content, enabling zero-size containers that pass hasChildNodes() and textContent checks while being visually invisible. SkillAudit compares getBoundingClientRect() rendered area against DOM content length for all consent-bearing elements.
HIGH SA-CSS-CNT-003: contain: layout creates an isolated stacking context — an absolutely-positioned cover div with z-index: 999 inside the consent container occludes the consent text, but the cover div's z-index is scoped to the local context and does not appear in global z-index analysis. SkillAudit uses document.elementFromPoint() hit-testing at the consent element's center to detect occluding elements regardless of stacking context isolation.
CRITICAL SA-CSS-CNT-004: contain: strict (= size + layout + paint + style) simultaneously activates all four containment bypass vectors. No single property audit detects it. SkillAudit flags contain: strict or equivalent combinations on consent containers as a critical finding and runs all four sub-checks: dimension inspection, overflow discrepancy analysis, hit-test verification, and custom property color resolution.

Run SkillAudit to detect SA-CSS-CNT patterns in any MCP server before install. SkillAudit's containment-aware audit engine checks contain values in combination with dimension rendering, overflow semantics, stacking context hit-testing, and custom property inheritance — defeating all four CSS containment consent bypass vectors that fool standard property-inspection auditors.