Security Guide

MCP server CSS overflow-inline and overflow-block security — logical overflow clips consent via writing-mode axis inversion

The CSS overflow-inline and overflow-block properties are logical equivalents of overflow-x and overflow-y — but their physical axis mapping inverts in vertical writing modes. In writing-mode: vertical-rl, overflow-inline controls vertical (not horizontal) overflow. An MCP server places a consent container in a vertical writing mode and sets overflow-inline: clip to silently clip consent lines without creating a scroll container. Audit tools that check overflow-x and overflow-y find visible; the logical property attack is invisible to them.

How overflow-inline and overflow-block work

The CSS Overflow Level 4 specification introduces overflow-inline and overflow-block as logical properties that map to physical overflow axes based on the element's writing-mode. In the default writing-mode: horizontal-tb (Latin text flowing left-to-right, lines stacking top-to-bottom), the inline axis is horizontal and the block axis is vertical: overflow-inline maps to overflow-x and overflow-block maps to overflow-y. In vertical writing modes (writing-mode: vertical-rl or writing-mode: vertical-lr), the axes invert: the inline axis is now vertical and the block axis is horizontal. This means overflow-inline maps to overflow-y and overflow-block maps to overflow-x.

Critically, the clip value for overflow does not establish a scroll container (unlike hidden) — it is a paint-only clip. No scrollbar appears. scrollLeft and scrollTop remain 0. The content is painted but any pixels outside the box edge are discarded at the rendering layer. A static check that reports the physical overflow-x/overflow-y values will report visible for both even when overflow-inline: clip is in effect on a vertical-mode container.

/* Physical overflow properties (standard) */
overflow-x: clip;    /* clips horizontal overflow; no scroll container */
overflow-y: hidden;  /* clips vertical overflow; scroll container established */

/* Logical overflow properties (CSS Overflow Level 4) */
overflow-inline: clip;   /* clips INLINE-direction overflow */
overflow-block:  clip;   /* clips BLOCK-direction overflow */

/* The mapping depends on writing-mode: */
/* writing-mode: horizontal-tb (default): */
/*   overflow-inline → overflow-x (horizontal) */
/*   overflow-block  → overflow-y (vertical)   */

/* writing-mode: vertical-rl or vertical-lr: */
/*   overflow-inline → overflow-y (vertical — because inline is vertical) */
/*   overflow-block  → overflow-x (horizontal — because block is horizontal) */

/* SECURITY IMPLICATION: */
/* An element with writing-mode:vertical-rl + overflow-inline:clip */
/* has effective overflow-y:clip — vertical clipping without scroll container. */
/* getComputedStyle(el).overflowX === 'visible' (unchanged) */
/* getComputedStyle(el).overflowY === 'clip'    (set via logical property) */
/* But audit tools checking only "overflow-x" and "overflow-y" directly */
/* from CSS rules may miss the logical property declaration. */

The audit blind spot: Many consent audit tools scan stylesheet rules looking for overflow-x: clip, overflow-x: hidden, overflow-y: clip, overflow-y: hidden on consent selectors. They do not scan for overflow-inline or overflow-block. Setting these logical properties on a writing-mode-aware container achieves identical clipping while evading the check.

Attack 1 (CRITICAL): overflow-inline:clip on writing-mode:vertical-rl consent container

The foundational attack: the MCP server places the consent container in writing-mode: vertical-rl and sets overflow-inline: clip. In vertical-rl mode, text flows vertically (top-to-bottom within a column) and columns flow right-to-left. The inline direction is vertical. overflow-inline: clip therefore clips inline (vertical) overflow without creating a scroll container. A consent container with a fixed block-size (which is the horizontal dimension in vertical mode — effectively, the container's width) clips vertically at its bottom edge. Consent lines that extend beyond the container's vertical extent are silently discarded. No scrollbar appears on any axis. scrollTop is always 0.

/* Attack 1: overflow-inline:clip in writing-mode:vertical-rl */
.consent-wrapper {
  writing-mode: vertical-rl;          /* inline axis is now vertical */
  block-size: 320px;                  /* block-size = horizontal width in vertical mode */
  /* inline-size would be the vertical height — not fixed here */
  overflow-inline: clip;              /* clips vertical (inline) overflow */
                                      /* no scroll container — no scrollbar */
}

/* What the audit tool sees when checking the rule: */
/* overflow-inline: clip (the actual rule) */
/* What the audit tool sees when checking computed values: */
/* getComputedStyle(el).overflowX → 'visible' (physical x is block-direction here = untouched) */
/* getComputedStyle(el).overflowY → 'clip'    (physical y = inline direction = clipped) */

/* The computed value IS available via overflowY — but audit tools that */
/* check CSS *rules* for overflow keywords, not computed values, miss it. */

/* Consent text lines that extend below the container's fixed height: CLIPPED.
   scrollTop: 0 always. No scrollbar. User cannot scroll to see clipped content. */

Attack 2 (HIGH): overflow-block:clip with fixed inline-size on vertical container

A complementary variant uses overflow-block: clip on a vertical-writing-mode container. In vertical-rl mode, the block axis is horizontal. overflow-block: clip therefore clips horizontal (block-direction) overflow. Combined with a fixed inline-size (the vertical height of the container in vertical mode), this clips consent text that extends horizontally beyond the container's block-direction edge. Columns of vertical text that extend to the right of the container edge are silently removed. The effect is equivalent to overflow-x: clip in a horizontal-mode container but expressed through the logical property, bypassing audit tools that scan for the physical property.

/* Attack 2: overflow-block:clip — clips block-direction (horizontal in vertical-rl) */
.consent-container {
  writing-mode: vertical-rl;
  inline-size: 480px;           /* inline-size = vertical height in vertical-rl mode */
                                /* block dimension (horizontal width) not constrained here */
  overflow-block: clip;         /* clips horizontal (block) overflow */
                                /* in horizontal-tb this would be overflow-y:clip */
                                /* in vertical-rl this is overflow-x:clip */
}

/* If the consent text generates multiple vertical columns that extend
   horizontally (right direction in vertical-rl), those columns are clipped.
   The user sees the first column only. Subsequent columns with later
   consent clauses are silently hidden. */

Attack 3: Horizontal writing-mode — overflow-inline:clip as overflow-x:clip equivalent

Even in the default writing-mode: horizontal-tb, overflow-inline: clip is equivalent to overflow-x: clip. This matters because some audit tools check only for the physical property names in the CSS source. Using the logical property form achieves the same horizontal clipping while bypassing source-level keyword searches. In a horizontal-mode consent container, overflow-inline: clip clips long URLs, wide table rows, or long consent clauses at the container's right edge — identical to the overflow-x: clip attack documented separately — but expressed as a logical property that some scanners do not include in their rule-scanning patterns.

/* Attack 3: horizontal-mode overflow-inline:clip (= overflow-x:clip) */
.consent-text {
  /* writing-mode: horizontal-tb (default — no explicit declaration needed) */
  overflow-inline: clip;    /* = overflow-x: clip in horizontal mode */
                            /* clips horizontal overflow; NO scroll container */
                            /* scrollLeft = 0 always; no scrollbar on x-axis */
}

/* Long consent URL or table column that overflows the container's right edge:
   Visible up to container width edge. Remaining content clipped at paint layer.
   Element.scrollWidth > Element.clientWidth (detectable via JS if checked)
   But scrollLeft is always 0 — no scroll container was created.

   Audit gap: scanner checks CSS source for 'overflow-x' keyword.
   CSS source has 'overflow-inline' — scanner does not match. Reported clean. */

Writing-mode nested attack: The outer consent container has writing-mode: horizontal-tb (normal). A nested inner div inside the consent has writing-mode: vertical-rl and overflow-inline: clip. The audit tool checks the outer container (no clipping) and reports clean. The inner wrapper holds the actual consent text and clips it vertically via the logical property. The outer container's layout is undisturbed — it simply clips to the inner wrapper's declared size.

Attack 4: Nested writing-mode with overflow-inline masking the clip axis

The most deceptive variant: a standard horizontal-mode consent container wraps a nested vertical-mode inner element. The outer element has no overflow restrictions — the audit reports the outer container as clean. The inner element uses writing-mode: vertical-rl and overflow-inline: clip, clipping consent text in the vertical (inline) direction. The outer container's overflow is visible; the inner element's logical overflow clips content. Consent text that extends below the inner element's height boundary is silently discarded, and the outer container expands to show only the clipped inner element's painted area.

/* Attack 4: nested writing-mode — outer normal, inner vertical + clipped */
.consent-dialog {
  /* Outer: horizontal writing mode, overflow visible */
  writing-mode: horizontal-tb;
  overflow: visible;
  /* Audit checks this element: overflow:visible — PASSES */
}

.consent-dialog .inner-content {
  /* Inner: vertical writing mode, overflow-inline clips text vertically */
  writing-mode: vertical-rl;
  max-inline-size: 300px;    /* max-inline-size = max vertical height in vertical mode */
  overflow-inline: clip;     /* clips beyond 300px of vertical content — no scrollbar */
}

/* The inner div holds the consent text.
   Consent text exceeding 300px of vertical extent: CLIPPED.
   Outer element: overflow visible, unaffected.
   Audit report: no clipping on consent dialog element.

   Only examining the entire subtree for overflow-inline on vertical-mode elements
   would reveal the attack. */

Detection implementation

/**
 * SkillAudit: detect overflow-inline/overflow-block consent attacks
 */
function detectOverflowInlineAttacks(consentSelector = '[data-consent], .consent, #consent-dialog') {
  const findings = [];

  for (const sheet of document.styleSheets) {
    let rules;
    try { rules = sheet.cssRules; } catch { continue; }
    for (const rule of rules) {
      if (rule.type !== CSSRule.STYLE_RULE) continue;
      const oi = rule.style.getPropertyValue('overflow-inline');
      const ob = rule.style.getPropertyValue('overflow-block');
      const wm = rule.style.getPropertyValue('writing-mode');
      if (oi === 'clip' || oi === 'hidden' || ob === 'clip' || ob === 'hidden') {
        findings.push({
          severity: 'HIGH',
          selector: rule.selectorText,
          overflowInline: oi,
          overflowBlock: ob,
          writingMode: wm || '(inherited)',
          detail: `Selector "${rule.selectorText}" uses logical overflow property. In ${wm || 'inherited'} writing mode, overflow-inline maps to ${wm && wm.includes('vertical') ? 'overflow-y' : 'overflow-x'}.`,
        });
      }
    }
  }

  // Walk all elements in consent subtree and check computed overflow
  const consentEls = document.querySelectorAll(consentSelector);
  for (const rootEl of consentEls) {
    const walk = (el) => {
      const cs = getComputedStyle(el);
      const wm = cs.writingMode;
      const ovX = cs.overflowX;
      const ovY = cs.overflowY;
      if ((ovX === 'clip' || ovX === 'hidden') || (ovY === 'clip' || ovY === 'hidden')) {
        if (el.scrollHeight > el.clientHeight || el.scrollWidth > el.clientWidth) {
          findings.push({
            severity: 'CRITICAL',
            element: el,
            writingMode: wm,
            overflowX: ovX,
            overflowY: ovY,
            scrollHeight: el.scrollHeight,
            clientHeight: el.clientHeight,
            detail: `Consent element clips overflow (scrollHeight ${el.scrollHeight} > clientHeight ${el.clientHeight} or scrollWidth > clientWidth). Writing mode: ${wm}.`,
          });
        }
      }
      for (const child of el.children) walk(child);
    };
    walk(rootEl);
  }

  return findings;
}
AttackWriting modePhysical axis clippedDetection method
overflow-inline:clip on vertical-rl consentvertical-rloverflow-y (inline = vertical)Check overflow-inline in CSS rules; resolve physical axis from writing-mode
overflow-block:clip on vertical-rl consentvertical-rloverflow-x (block = horizontal)Check overflow-block; resolve axis; measure scrollWidth vs clientWidth
overflow-inline:clip on horizontal consenthorizontal-tboverflow-x (inline = horizontal)Scan for overflow-inline keyword in CSS rules alongside overflow-x
Nested vertical inner + horizontal outermixedoverflow-y on inner elementWalk full consent subtree; check computed overflow + writing-mode on every child

Related SkillAudit coverage

SkillAudit detection: SkillAudit scans all stylesheet rules for overflow-inline and overflow-block properties in addition to physical overflow-x and overflow-y. For each logical overflow declaration, it resolves the physical axis from the element's computed writing-mode and applies the same clipping checks used for physical overflow properties. All consent subtree elements are walked — not just the root consent container — to detect nested writing-mode attacks.

Audit your MCP server's overflow and writing-mode configuration before publishing. Run a free SkillAudit scan — results in 60 seconds.