Security Guide

MCP server CSS field-sizing security — field-sizing:content collapsing form element disclosures, font-size:0 textarea collapse, field-sizing:fixed tiny dimensions, select element collapse

CSS field-sizing (Chrome 123+) controls whether form elements size to their content (field-sizing:content) or to explicit dimensions (field-sizing:fixed). Consent dialogs that render disclosures inside <textarea>, <select>, or <input> elements are vulnerable: an MCP server can exploit field-sizing:content combined with other CSS to collapse these form elements to zero size while standard height and display guards report normal values.

CSS field-sizing — overview

field-sizing is a CSS property that changes how form elements compute their intrinsic size. The default value field-sizing:fixed (equivalent to historical behavior) sizes form elements according to their rows/cols/size HTML attributes or CSS width/height. The new value field-sizing:content makes the element dynamically resize to fit its actual content — a textarea with one line of text becomes one line tall, an empty textarea collapses toward its minimum. Attacks arise when an MCP server can inject CSS that alters the form element's size-governing properties alongside field-sizing:content.

Attack 1: field-sizing:content on <textarea> + font-size:0 — textarea collapses to zero height because each row is zero-height

A readonly <textarea> is sometimes used in consent dialogs to display the full permission text in a scrollable box. With field-sizing:content, the textarea's height is determined by its content's natural height — which is computed from line-height, font-size, and the number of text rows. Setting font-size:0px makes each character zero-height. With field-sizing:content, a textarea whose content has zero character height sizes to zero height:

/* MCP server: field-sizing:content + font-size:0 collapses readonly textarea to zero height */

textarea.permission-disclosure {
  field-sizing: content;  /* size to content, not to rows attribute */
  font-size: 0px;          /* text renders at 0px height per character */
  line-height: 0;          /* zero line height */
  padding: 0;              /* remove default textarea padding */
  border: 0;               /* remove border (contributes to rendered height) */
  /* Result: content height = (number of rows) × (line-height=0) = 0px
     With field-sizing:content: textarea height = 0px
     The textarea collapses to zero height. */
}

/* What guards see:
   getComputedStyle(textareaEl).display: 'block' (not none)
   getComputedStyle(textareaEl).visibility: 'visible'
   textareaEl.textContent: full disclosure text (unchanged)
   textareaEl.value: full disclosure text (unchanged)
   getBoundingClientRect().height: 0 (collapses via field-sizing:content)
   A guard that checks getBoundingClientRect().height catches this.
   But a guard that only checks display/visibility/opacity misses it entirely.

   Note: textareaEl.offsetHeight is also 0 when field-sizing:content collapses the element.
   textareaEl.rows: still set to the original value (e.g., 10).
   The rows attribute is overridden by field-sizing:content + zero font-size. */

Font-size:0 is the amplifier: Without font-size:0, field-sizing:content on a textarea containing several lines of text would expand the textarea to fit all content — the attack would make it taller, not smaller. The attack requires font-size:0 (or equivalently line-height:0) to turn the content into zero-height lines. Combined with field-sizing:content, the textarea then sizes to zero. The textContent and value are both readable by JavaScript — the text is present in the DOM. Only the rendered height collapses.

Attack 2: field-sizing:content + min-height:0 + padding:0 + line-height:0 — removing the browser minimum floor from the disclosure textarea

Without field-sizing:content, browsers enforce a minimum height on textarea elements based on the rows attribute (minimum 1 row). With field-sizing:content, the size is driven entirely by content metrics. If those metrics are zeroed out, no minimum floor protects the element from collapsing:

/* MCP server: field-sizing:content + min-height:0 removes textarea size floor */

textarea.permission-disclosure {
  field-sizing: content;
  min-height: 0 !important;    /* removes the browser's rows-based minimum */
  line-height: 0 !important;   /* zero line height → zero content height per row */
  padding-top: 0 !important;   /* removes vertical padding (browser UA padding ~2px) */
  padding-bottom: 0 !important;
  border-width: 0 !important;  /* border contributes to offsetHeight */
  /* The combination:
     - field-sizing:content means height is NOT determined by rows or CSS height
     - min-height:0 removes the floor that field-sizing:content respects
     - line-height:0 collapses each text row to zero height
     - padding/border zero removes the non-content contributions
     Result: getBoundingClientRect().height = 0 */
}

/* Variant using height:0 directly (bypasses field-sizing entirely, simpler attack):
   If an MCP can inject 'height:0;overflow:hidden' on a textarea, field-sizing is irrelevant.
   The field-sizing attack is valuable specifically because:
   1. It targets the form element's INTRINSIC SIZE mechanism, not a hard-coded height
   2. Some style frameworks block 'height' overrides on form elements but allow 'field-sizing'
   3. The attack can be applied via a general 'textarea { field-sizing:content; font-size:0 }' rule
      targeting all textareas — a seeming developer preference, not a targeted attack

   Guards must check getBoundingClientRect().height > 0 on ALL form elements
   inside consent dialogs, not just div/p elements. */

Attack 3: field-sizing:fixed with tiny explicit width and height — explicit tiny dimension override

field-sizing:fixed is the CSS default for form elements, but it enables explicit CSS width and height properties to control the element's size (historically, form element width/height was set by HTML attributes). With field-sizing:fixed and explicit CSS dimensions, an MCP can collapse a disclosure textarea to a near-invisible 1×1 pixel:

/* MCP server: field-sizing:fixed with tiny explicit dimensions */

textarea.permission-disclosure {
  field-sizing: fixed;   /* explicit CSS width/height controls size (this IS the default) */
  width: 1px;            /* 1px wide */
  height: 1px;           /* 1px tall */
  overflow: hidden;      /* clip the content that no longer fits */
  resize: none;          /* prevent user from dragging resize handle to reveal content */
  /* Result: textarea is 1×1 pixels. Content overflows into overflow:hidden clip zone.
     Text is inaccessible to the user — only 1 character might be partially visible. */
}

/* Variant: use field-sizing:fixed with max-height:0 */
textarea.permission-disclosure {
  field-sizing: fixed;
  max-height: 0px;       /* hard max-height: zero */
  overflow: hidden;
  padding: 0;
  border: 0;
  /* Equivalent to height:0 but using max-height which some guards don't check directly */
}

/* What guards see:
   getBoundingClientRect().height: 1 (or 0 with max-height:0 variant)
   A height > 0 check with threshold of "disclosureEl.scrollHeight > 50" would catch this:
   textareaEl.scrollHeight: returns the full content height regardless of visible height
   scrollHeight significantly > getBoundingClientRect().height indicates overflow:hidden clipping
   This ratio check (scrollHeight / clientHeight) catches field-sizing:fixed + tiny-height attacks. */

resize:none as a cover: Without resize:none, the user could drag the textarea's resize handle to make the content visible. Adding resize:none removes this escape. Many consent dialog implementations already set resize:none on readonly textareas for UX reasons — an MCP can rely on this being already set, making the resize:none injection unnecessary (and its absence in the injected CSS harder to flag).

Attack 4: field-sizing:content on <select> element with font-size:0 + padding:0 — select collapses to zero apparent size

Some consent dialogs use a <select> element with multiple attribute to display a scrollable list of permissions being granted. field-sizing:content on a <select multiple> makes the select size to its content options. Combining with font-size:0 collapses each option to zero height:

/* MCP server: field-sizing:content + font-size:0 on a select element with permission list */

select.permission-list {
  field-sizing: content;
  font-size: 0px;          /* each option renders at 0px height */
  line-height: 0;
  padding: 0;
  border: 0;
  /* For a 
     CSS can't directly set the size attribute, but can use: */
  height: 0px;
  overflow: hidden;
  /* This works without field-sizing — the select is collapsed to 0px height
     and its options are in the overflow:hidden clip zone. */
}

/* Variant B: appearance:none + collapse (hides the select widget entirely) */
select.permission-list {
  -webkit-appearance: none;
  appearance: none;        /* removes the native select widget chrome */
  width: 0;
  height: 0;
  overflow: hidden;
  opacity: 0;              /* also zeroed for belt-and-suspenders */
  /* Full removal: no native widget, no dimensions, invisible.
     The