MCP server CSS overflow-clip-margin-block security: block axis clip without scrollbar, vertical writing mode consent clipping, start-edge-only clip, and overflow paint outside container

Published 2026-09-25 — SkillAudit Research

The CSS property overflow-clip-margin-block is a logical-axis variant of overflow-clip-margin. It controls how far content is allowed to paint outside an element's border box along the block axis when overflow: clip is active. The block axis is vertical in standard horizontal writing modes (writing-mode: horizontal-tb) and horizontal in vertical writing modes (writing-mode: vertical-rl or vertical-lr).

Unlike overflow: hidden, which creates a scroll container ancestor, overflow: clip clips content without creating a scroll container. This means clipped content is not accessible via scrolling — there is no scrollbar, no overflow affordance, no visual indicator that content exists beyond the clip boundary. Combined with overflow-clip-margin-block: 0px, which sets the clip boundary exactly at the block-axis border edge, an MCP server can silently remove consent content from view in vertical writing modes or cause block-axis overflow to be clipped invisibly in any writing mode.

overflow:clip vs overflow:hidden: overflow: hidden creates a block formatting context and a scroll container. Content overflowing a hidden container may be accessible to the user by inspecting the element or by scrolling a parent. overflow: clip performs hard clipping with no scroll container. The clipped content cannot be accessed by any user interaction. Audit tools that check for scrollable overflow will not find the clipped content. The DOM textContent is intact; only the paint is suppressed.

Attack 1: overflow:clip + overflow-clip-margin-block:0px in vertical writing mode

In writing-mode: vertical-rl, text flows top-to-bottom along the block axis, which runs left-to-right on screen. The inline axis (the direction text flows within a line) is vertical. Setting overflow: clip with overflow-clip-margin-block: 0px clips block-axis overflow — which in vertical-rl means any content that extends to the right of the element's border box is clipped. Consent text that flows into a new block-direction line (wraps to the right in vertical-rl) is cut off at the border box edge with no scrollbar.

/* Consent dialog in vertical writing mode */
.consent-section {
  writing-mode: vertical-rl;
  width: 200px;       /* Block axis in vertical-rl: width dimension */
  overflow: clip;
  overflow-clip-margin-block: 0px;
  /* consent-section contains 800px of text flowing right.
     With width:200px and overflow:clip + clip-margin-block:0px:
     Text wraps at block-axis boundary (200px from left).
     Block-axis overflow is clipped at 200px.
     The remaining 600px of consent text is invisible.
     No scrollbar — overflow:clip does not create scroll container.
     DOM textContent: intact. Visual: first 200px of consent visible. */
}

/* Detection: check overflow:clip + overflow-clip-margin-block on elements
   with writing-mode that is not horizontal-tb */
function detectVerticalWritingClip(root) {
  const findings = [];
  const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
  let el;
  while (el = walker.nextNode()) {
    const cs = window.getComputedStyle(el);
    if (cs.overflow !== 'clip') continue;
    const wm = cs.writingMode;
    if (wm === 'vertical-rl' || wm === 'vertical-lr' || wm === 'sideways-rl') {
      const clipMarginBlock = cs.getPropertyValue('overflow-clip-margin-block');
      const parsed = parseFloat(clipMarginBlock);
      if (!isNaN(parsed) && parsed <= 0) {
        findings.push({
          element: el,
          writingMode: wm,
          overflowClipMarginBlock: clipMarginBlock,
          scrollWidth: el.scrollWidth,
          clientWidth: el.clientWidth,
          note: 'vertical writing mode + overflow:clip + overflow-clip-margin-block:0 — block-axis consent overflow clipped invisibly',
        });
      }
    }
  }
  return findings;
}

Attack 2: overflow-clip-margin-block-start:0px — asymmetric start-edge clipping

CSS logical property shorthand overflow-clip-margin-block has individual longhand properties for each edge: overflow-clip-margin-block-start and overflow-clip-margin-block-end. Setting only the start edge to zero while leaving the end edge at its default (which may be non-zero or may match the element's content extent) creates asymmetric clipping. In vertical writing modes, the block-start edge is the top of the element in vertical-tb, but the left edge in vertical-rl. Setting overflow-clip-margin-block-start: 0px clips overflow on one side of the block axis while allowing overflow on the other.

/* Asymmetric block-axis clipping */
.consent-dialog {
  writing-mode: vertical-rl;
  overflow: clip;
  overflow-clip-margin-block-start: 0px;   /* Left edge: hard clip */
  overflow-clip-margin-block-end: 100px;   /* Right edge: 100px allowance */
  /* In vertical-rl: text flows left-to-right.
     Block-start edge is left. Block-end edge is right.
     Content overflowing to the right: allowed up to 100px past border.
     Content overflowing to the left: hard-clipped at border.

     If the consent container has a negative margin-left or a transform
     that shifts content left, the overflow-clip-margin-block-start:0 means
     that leftward overflow (block-start direction) is hard-clipped.
     The consent text that flows to the left of the border box is invisible. */
}

/* Less obvious variant: standard horizontal-tb writing mode.
   In horizontal-tb, block-start = top, block-end = bottom.
   overflow-clip-margin-block-start:0 clips top-side overflow.
   Content that is positioned above the element's top border (via negative margin
   or relative positioning) is clipped. If the consent section uses a
   negative top margin to position into the space above the dialog, that
   content is clipped. */
.consent-popup {
  writing-mode: horizontal-tb;
  overflow: clip;
  overflow-clip-margin-block-start: 0px;
  /* Clips any content that paints above the border-top of .consent-popup.
     If consent text uses position:relative + top:-40px to move upward,
     the part above the border box is clipped. */
}

Attack 3: large overflow-clip-margin-block painting consent outside scroll container

The inverse attack uses a large overflow-clip-margin-block value on an ancestor container. When the consent element overflows its parent and the parent has overflow: clip, the clip margin determines how far the overflow can paint. A large value (e.g., overflow-clip-margin-block: 100vw) allows the overflow to paint far outside the container — past the visible edges of the scroll area. Combined with a zero-height consent container and negative margins, the consent text is painted outside the scrollable region entirely, in a paint zone the user would never scroll to.

/* Outer container: allows large block-axis overflow */
.dialog-outer {
  height: 200px;
  overflow: clip;
  overflow-clip-margin-block: 100vw;  /* Allow overflow 100vw below bottom edge */
}

/* Inner: zero height, consent text displaced far below */
.consent-inner {
  height: 0;
  overflow: visible;
  /* consent text uses position:relative + top: 250px to sit just outside the
     dialog-outer box. The dialog-outer overflow-clip-margin-block:100vw
     allows paint 100vw below the bottom edge — so the text "renders"
     at 250px below dialog-outer's bottom border.
     But 250px below the dialog-outer is outside the visible viewport
     if dialog-outer is placed at the bottom of the page.
     The text renders in the DOM, passes textContent checks, passes
     getBoundingClientRect() — rect.top will be a large positive value
     below the fold — but is never seen by the user in normal scrolling. */
}

Attack 4: overflow-clip-margin-block in standard writing mode — top/bottom consent crop

In standard horizontal-tb writing mode, overflow-clip-margin-block controls vertical (block-axis) clip margin. Setting it to zero alongside overflow: clip on a consent section that has a defined height shorter than its content height clips the bottom portion of the text without a scrollbar. The last lines of the consent text — including the acceptance clause — are cut off cleanly at the bottom border of the element, with no scroll affordance.

/* Consent section height shorter than content, overflow:clip */
.terms-block {
  height: 80px;          /* Only enough for the first 3 lines */
  overflow: clip;
  overflow-clip-margin-block: 0px;  /* Hard clip at bottom border */
  /* Content: 400px of consent text.
     Visible: first 80px (approximately 4 lines at 20px line-height).
     Remaining 320px: clipped — no scrollbar, no visual indicator.
     The acceptance clause at the bottom is never visible.
     DOM textContent: intact.
     If the MCP UI checks scrollHeight (400px) vs clientHeight (80px),
     scrollHeight > clientHeight should indicate overflow.
     But with overflow:clip, the element is NOT a scroll container,
     so standard "has the user scrolled to the bottom" checks that
     use scrollTop + clientHeight >= scrollHeight will ALWAYS
     evaluate to: 0 + 80 < 400 → user has NOT scrolled — which blocks
     the Accept button. The accept button is permanently disabled.
     This is an alternative use of this property: blocking the accept
     flow rather than silently hiding content. */
}

/* Detection */
function detectClipWithBlockMarginZero(root) {
  const findings = [];
  const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
  let el;
  while (el = walker.nextNode()) {
    const cs = window.getComputedStyle(el);
    if (cs.overflow !== 'clip' && cs.overflowY !== 'clip') continue;
    const marginBlock = cs.getPropertyValue('overflow-clip-margin-block');
    const parsed = parseFloat(marginBlock);
    const hasZeroMargin = !isNaN(parsed) && parsed <= 0;
    if (el.scrollHeight > el.clientHeight + 5) {
      findings.push({
        element: el,
        scrollHeight: el.scrollHeight,
        clientHeight: el.clientHeight,
        overflow: cs.overflow,
        clipMarginBlock: marginBlock,
        note: hasZeroMargin
          ? 'overflow:clip + overflow-clip-margin-block:0 with scroll content — block-axis content clipped without scrollbar'
          : 'overflow:clip with block-axis content overflow — scroll content clipped',
      });
    }
  }
  return findings;
}

Summary

AttackMechanismSeverityDetection method
HIGHVertical writing mode block-axis clip
writing-mode:vertical-rl + overflow:clip + overflow-clip-margin-block:0 Block-axis overflow in vertical text silently removed; no scrollbar; DOM intact Check overflow:clip on non-horizontal-tb writing mode elements; compare scrollWidth vs clientWidth
HIGHAsymmetric start-edge clipping
overflow-clip-margin-block-start:0 clips only one block edge Content displaced toward block-start edge is clipped; partial consent visible at block-end; harder to detect than full clip Check individual longhand properties: overflow-clip-margin-block-start and -end separately
MEDIUMLarge clip margin outside scroll container
overflow-clip-margin-block:100vw allows paint in zone user never scrolls Consent paints outside scroll container in unreachable region; getBoundingClientRect shows valid position but below fold Check elements with positive rect.top > window.innerHeight + 200; verify clip-margin interaction
MEDIUMHeight-constrained block clip without scrollbar
Fixed height shorter than content + overflow:clip blocks scroll-to-bottom check Accept button permanently disabled OR lower consent text permanently invisible; scrollHeight > clientHeight but no scroll possible On all overflow:clip elements: check scrollHeight > clientHeight; flag absence of scrollbar affordance

See also: CSS overflow-clip-margin security for the shorthand (both axes) attack surface and CSS overflow-clip-margin-inline security for the inline-axis variant.

SkillAudit detects overflow:clip consent hiding attacks in its runtime consent audit. Start a free scan.