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
| Attack | Prerequisite | What it enables | Severity |
|---|---|---|---|
| field-sizing:content + font-size:0 on disclosure textarea — textarea collapses to zero height because content height is zero per row | Consent dialog renders disclosure in readonly textarea; MCP injects field-sizing:content + font-size:0 + line-height:0 + padding:0 + border:0 on the textarea; browser computes content height as zero; field-sizing:content makes textarea collapse to zero; full text remains in textarea.value (DOM intact) | Disclosure textarea collapses to 0px rendered height; getBoundingClientRect().height is 0; textarea.value and textContent are unchanged; guards checking opacity/visibility/display pass; only a getBoundingClientRect().height check on form elements catches this | HIGH |
| field-sizing:content + min-height:0 + line-height:0 — removing the rows-attribute minimum floor from the disclosure textarea | MCP injects field-sizing:content + min-height:0 + padding:0 + border:0 + line-height:0; removes all contributions to rendered height; textarea collapses; textarea.scrollHeight remains equal to natural content height proving hidden content exists | Same collapse as Attack 1 via a different property combination; the min-height:0 is the key amplifier removing the browser's rows-based protection; scrollHeight vs clientHeight ratio check detects the overflow | HIGH |
| field-sizing:fixed with explicit 1px width and height on disclosure textarea — explicit tiny dimension collapse | MCP injects field-sizing:fixed with width:1px height:1px overflow:hidden resize:none; textarea is 1×1 pixels; content overflows into clip zone; user cannot resize; scrollHeight remains full content height but clientHeight is 1 | Disclosure is effectively invisible (1×1 pixels); user cannot interact with the content; resize handle is blocked; scrollHeight/clientHeight ratio > threshold catches this; text remains in DOM | HIGH |
| field-sizing:content on select element + font-size:0 — select option rows collapse to zero height, select collapses | Consent dialog renders permissions as select multiple options; MCP injects field-sizing:content + font-size:0 on the select; each option is zero height; select sizes to content = zero; all options remain in the options collection (DOM intact) | Permission select is 0px rendered height; all options are accessible via JavaScript (.options collection) but not visually; getBoundingClientRect().height is 0 on the select element; guard must include select elements in the height check | HIGH |
Defences
- CSP
style-srcwith nonce. Prevents injected<style>blocks from settingfield-sizing,font-size:0, ormin-height:0on form elements in consent dialogs. - Check
getBoundingClientRect().heighton ALL form elements inside the dialog, not just div/p. Textarea, select, and input elements are often excluded from consent visibility guards. A guard must iterate all elements (including form elements) and check their rendered height. - Check
scrollHeightvsclientHeightratio. Atextareawith content butclientHeight:0hasscrollHeightequal to the natural content height. A ratio check wherescrollHeight > 10 × clientHeightreliably indicates clipped content. - Lock form element sizing in trusted stylesheets. A trusted stylesheet setting
textarea, select { min-height: 100px !important; font-size: inherit !important; field-sizing: fixed !important; }on dialog form elements prevents collapse attacks. - SkillAudit flags:
field-sizing:contenton consent dialog form elements;font-size:0orline-height:0on form elements;min-height:0on textarea; form elementgetBoundingClientRect().heightof 0 or near-0;scrollHeightsignificantly exceedingclientHeighton form elements.
SkillAudit findings for this attack surface
Related: CSS width security covers zero-width collapse attacks on non-form elements. CSS overflow security covers overflow:hidden clipping that field-sizing:fixed collapse attacks depend on. CSS font-size-adjust security covers other font-metric manipulation attacks.