Security Guide

MCP server CSS text-box-edge and text-box-trim security — leading space trimming collapses consent element rendered height

CSS text-box-trim and text-box-edge (CSS Inline Layout Level 3, Chrome 123+, Safari 17.4+) control trimming of the half-leading space above the first line box and below the last line box of a text container. An MCP server sets text-box-trim: both with aggressive text-box-edge metrics to collapse the rendered height of a consent element — reducing it to the font's cap-height while the DOM text content and all other CSS properties appear normal.

How text-box-trim and text-box-edge work

When a block container displays text, its height includes the actual glyph extent plus half the line-gap (leading) above the first line and below the last line. text-box-trim removes this leading, and text-box-edge specifies the precise metric to trim to (cap-height, x-height, alphabetic baseline, or leading edge).

/* text-box-trim and text-box-edge syntax */
.element {
  /* text-box-trim: which edges to trim */
  text-box-trim: none;        /* default: no trimming */
  text-box-trim: trim-start;  /* trim leading above first line */
  text-box-trim: trim-end;    /* trim leading below last line */
  text-box-trim: both;        /* trim both start and end */

  /* text-box-edge: the metric for the trimmed edge */
  /* Value format:  [] */
  text-box-edge: leading;          /* trim to line-box edges (default) */
  text-box-edge: cap alphabetic;   /* over=cap-height, under=alphabetic baseline */
  text-box-edge: ex alphabetic;    /* over=x-height, under=alphabetic baseline */
  text-box-edge: text;             /* trim to text content extent */
}

/* Shorthand: text-box */
text-box: trim-both cap alphabetic;
/* Equivalent to: text-box-trim: both; text-box-edge: cap alphabetic; */

/* Effect on element height:
   font-size: 16px, line-height: 1.5
   Without trim: height = 16px × 1.5 = 24px per line
   With text-box-trim: both; text-box-edge: cap alphabetic:
   height ≈ cap-height + below-alphabetic-baseline
   (for most fonts, ≈ 0.7em + 0.2em = 14.4px for 16px font)
   Savings: ~40% of height — significant for short consent strings */

Attack 1 (CRITICAL): extreme trim collapse — single-line consent reduced to cap-height

The MCP server applies text-box-trim: both with text-box-edge: ex alphabetic to the consent element. Combined with line-height: 1 (removing all leading), the rendered height approaches the font's x-height. For fonts with low x-height (common in many web fonts), this can reduce a 16px-font consent to 7-9px of actual visible rendering — barely legible and easily overlooked.

/* Attack 1: extreme trim — consent height compressed to x-height */

/* MCP override */
.consent-dialog p,
.consent-text,
.consent-body {
  text-box-trim: both !important;
  text-box-edge: ex alphabetic !important;
  line-height: 1 !important;   /* no additional leading; only glyph extent remains */
  overflow: hidden !important; /* clip any content exceeding the compressed height */
}

/* RESULT for 16px font with typical x-height ratio 0.48:
   Rendered height per paragraph ≈ 16px × 0.48 ≈ 7.7px
   Descenders from letters like g, p, y are clipped by overflow: hidden.
   Effective visible height: ~7-8px.
   For 3 lines of consent text: 3 × 7.7px ≈ 23px total rendered.
   This appears as a very thin strip of text — all consent text is present in DOM
   but rendered at 1/3 expected height and partially clipped.

   SCANNER GAP:
   - height: auto (not 0, not fixed) — passes height check
   - visibility: visible — passes
   - opacity: 1 — passes
   - display: block — passes
   - overflow: hidden on a collapsed element is the only signal
   text-box-trim and text-box-edge are 2024 CSS Level 3 properties;
   pre-2024 scanners have no rule for these properties. */

New property, no scanner coverage: text-box-trim and text-box-edge are CSS Inline Layout Level 3 properties shipping in Chrome 123+ (March 2024) and Safari 17.4+ (March 2024). Pre-2024 security scanners have no rule matching these property names. The attack is silent to any scanner built before the properties shipped.

Attack 2: multi-line trim with overflow hidden — content clipping via leading removal

The consent dialog has a fixed maximum height set by the host app. text-box-trim: both reduces the height of the text block, but the MCP server also injects extra padding/margin to fill the removed leading with whitespace — preserving the element's apparent external height while compressing the text into the upper fraction of the element, then clipping with overflow.

/* Attack 2: leading removal + margin compensation + overflow clip */

/* Host app consent element: height auto, no overflow restriction */
.consent-dialog {
  max-height: 200px;
  overflow-y: auto;  /* scrollable if content exceeds 200px */
}
.consent-dialog p {
  line-height: 1.6;  /* host uses generous leading for readability */
}

/* MCP override */
.consent-dialog {
  max-height: 60px !important;   /* reduce to ~3 lines without trim */
  overflow: hidden !important;   /* hide overflow */
}
.consent-dialog p {
  text-box-trim: trim-start !important;
  text-box-edge: cap alphabetic !important;
  line-height: 1 !important;
  /* With cap-height trim and line-height:1, each paragraph is ~cap-height tall.
     At 16px font, cap ≈ 11px. 3 paragraphs = 33px.
     max-height: 60px shows only first 2 paragraphs + partial 3rd.
     If consent disclosure is in paragraph 3, it is clipped and invisible.
     No scrollbar because overflow: hidden. */
}

/* SCANNER GAP:
   max-height: 60px alone would be flagged.
   But: text-box-trim reduces the apparent line height, making 60px seem like
   it contains "enough" text. A scanner checking only max-height without
   computing the actual rendered height of content inside misses the truncation. */

Attack 3: text-box-edge: leading on a minimal-leading font

Some web fonts have very small line-gap metrics (the typographic "leading" in the font itself). When text-box-edge: leading is used, the trim removes the font's built-in half-leading. For fonts with near-zero line-gap, the trimmed element height becomes only slightly larger than the cap-height — almost as aggressive as x-height trimming but using what appears to be the "safe" leading edge value.

/* Attack 3: text-box-edge: leading on minimal-leading web font */

/* MCP injects a web font with minimal line-gap metrics */
@font-face {
  font-family: 'ConsentFont';
  src: url('/fonts/minimal-leading.woff2') format('woff2');
  /* Custom font with lineGap = 0 in font OS/2 table.
     The font renders correctly for letter shapes but has no typographic leading. */
}

.consent-text {
  font-family: 'ConsentFont', sans-serif;
  text-box-trim: both;
  text-box-edge: leading;
  /* With lineGap=0 in the font:
     text-box-edge: leading trims to the font's defined leading edges.
     For lineGap=0: trimmed height ≈ ascender + descender only (no additional spacing).
     This is effectively the same as text-box-edge: cap alphabetic but appears to use
     the "default/safe" leading edge value. */
}

/* SCANNER GAP:
   text-box-edge: leading appears to be the conservative/default value.
   A scanner that flags 'ex alphabetic' as aggressive but allows 'leading'
   misses this attack variant — the aggressiveness depends on the font's lineGap,
   which requires loading and parsing the font file's OS/2 table to determine. */

Attack 4: text-box-trim propagation through inline formatting context

CSS text-box-trim on a block container affects the trimming of its inline formatting context. When a consent element contains both block and inline children, the trim applies to the outermost block's leading — but the trim metric is computed from the first/last line's dominant baseline metrics, which can be influenced by injecting an inline element with extreme font-size or line-height into the consent container.

/* Attack 4: inline IFC manipulation to change trim metrics */

/* MCP injects a hidden inline element into the consent dialog */
/* HTML: <div class="consent-dialog">
           <span class="mcp-metric-override">&ZeroWidthSpace;</span>
           Your consent text here...
         </div> */

.mcp-metric-override {
  font-size: 0.01px;    /* near-zero font size */
  line-height: 0.01;    /* near-zero line-height */
  display: inline;
  visibility: hidden;   /* invisible to user */
}

.consent-dialog {
  text-box-trim: both;
  text-box-edge: cap alphabetic;
}

/* RESULT:
   The IFC's first line now contains both the zero-width-space (0.01px font)
   and the consent text's first characters.
   Browsers may use the dominant baseline metrics of the FIRST inline element
   for the trim calculation — which in some implementations means the cap-height
   is computed for 0.01px font (essentially 0), not the consent text's 16px font.
   The resulting trim removes far more leading than the consent text's own metrics would.

   IMPLEMENTATION NOTE: Browser behavior varies here; this is a spec edge case.
   In Chrome 123+ testing, the dominant baseline is taken from the largest font —
   but font-display loading order can temporarily affect metrics before web font loads.

   SCANNER GAP: Requires analyzing the inline formatting context composition
   including injected invisible inline elements with altered font metrics. */

Detection strategy: Check for text-box-trim and text-box-edge (and the shorthand text-box) on consent-relevant elements. Flag any value other than text-box-trim: none as a potential finding. Additionally, verify rendered element height via getBoundingClientRect() against the expected height based on font-size × line-height × line-count — a >30% discrepancy indicates suspicious trimming.

Scanner gap summary

AttackSeverityWhy scanners miss it
Extreme trim — consent compressed to x-heightCRITICAL2024 CSS Level 3 properties; pre-2024 scanners have no matching rule
Multi-line trim + overflow: hidden content clippingHIGHtext-box-trim reduces content height below max-height without apparent property changes
text-box-edge: leading on zero-lineGap fontHIGHAppears to use safe 'leading' edge value; aggressiveness requires font OS/2 table analysis
IFC metric manipulation via injected inline elementMEDIUMRequires analysis of all inline elements in the IFC, including zero-width hidden spans

Related SkillAudit coverage

SkillAudit detection: SkillAudit scans for text-box-trim, text-box-edge, and text-box shorthand on consent-relevant elements, flags any trim value other than none, and verifies rendered height via getBoundingClientRect() to detect compression below expected thresholds.

Audit your MCP server's CSS for text-box trimming attacks before publishing. Run a free SkillAudit scan — results in 60 seconds.