Security reference · CSS logical properties · Max-height collapse · Consent hiding
MCP server CSS max-block-size security
CSS max-block-size is the CSS Logical Properties equivalent of max-height in horizontal writing modes. Setting max-block-size: 0 with overflow: hidden collapses a consent element to zero visible height — producing the same visual result as max-height: 0; overflow: hidden without the string max-height appearing anywhere in the stylesheet. Physical-property scanners and auditors who check getComputedStyle().maxHeight directly will see the resolved value (correctly 0px), but string-based CSS source scanners checking for max-height miss it entirely. Four attack patterns: direct collapse, 1px hairline variant, custom property obfuscation, and JS class-toggle at mousedown with CSS height transition.
CSS max-block-size property fundamentals
In the CSS Logical Properties specification, the physical dimensions width and height are generalized to inline-size and block-size, which map to the physical axes relative to the current writing mode. In horizontal writing modes (writing-mode: horizontal-tb, the default for all Latin-script content), block-size corresponds to height and inline-size corresponds to width. Consequently, max-block-size maps to max-height and min-block-size maps to min-height. The browser resolves these to physical property values in computed style — getComputedStyle(el).maxHeight correctly returns the value set via max-block-size.
| Logical property | Physical equivalent (horizontal) | Computed style accessor | String "max-height" in source? |
|---|---|---|---|
| max-block-size: 0 | max-height: 0 | getComputedStyle().maxHeight → "0px" | No |
| max-block-size: 1px | max-height: 1px | getComputedStyle().maxHeight → "1px" | No |
| max-block-size: var(--h) | max-height: (resolved) | getComputedStyle().maxHeight → resolved value | No |
| max-height: 0 | max-height: 0 | getComputedStyle().maxHeight → "0px" | Yes |
Attack surface: SA-CSS-MBSZ-001 — direct max-block-size:0 collapse
max-block-size: 0; overflow: hidden collapses the consent element to zero rendered height. The element still participates in layout (not removed like display:none), occupies its natural horizontal space, and may have visible left/right borders or a top border hairline if border-width exceeds 0. Text content is in the DOM and read by accessibility APIs. The computed value of maxHeight is 0px — detectable via computed style — but the property name max-height never appears in any stylesheet.
/* MCP inject — collapses consent without max-height keyword */
.mcp-consent-section {
max-block-size: 0;
overflow: hidden;
/* "max-height" not in stylesheet — keyword scanner misses this */
}
Detection: parseFloat(getComputedStyle(consentEl).maxHeight) === 0 combined with el.scrollHeight > 0 (content exists but is fully collapsed). Always read from computed style, not from source CSS.
Attack surface: SA-CSS-MBSZ-002 — 1px hairline variant
max-block-size: 1px; overflow: hidden clips consent text to a 1px visible band at the top of the element. At 14–16px line-height, a 1px clip shows only the very top pixel of the first line's ascenders — a thin stroke that resembles a decorative divider, not legible text. This variant passes any check of the form maxHeight === '0px' since the value is 1px, not 0px. At most viewing distances and screen densities, the 1px visible hairline is not interpretable as consent text content.
/* SA-CSS-MBSZ-002: 1px hairline — passes "is it zero" check */
.mcp-disclosure-panel {
max-block-size: 1px;
overflow: hidden;
/* getComputedStyle().maxHeight: "1px" — not "0px" — passes zero-check */
}
Detection threshold: parseFloat(getComputedStyle(consentEl).maxHeight) < 12 with el.scrollHeight > 20 — any max-height below one line-height that still has content behind it indicates this pattern.
Attack surface: SA-CSS-MBSZ-003 — CSS custom property obfuscation
The logical property attack is combined with custom property indirection: --mcp-dialog-height: 0; max-block-size: var(--mcp-dialog-height). The computed value of maxHeight is still 0px — the browser resolves both layers — but a static CSS source scanner looking for max-block-size: 0 finds only max-block-size: var(--mcp-dialog-height). The custom property definition may be in a separate stylesheet, an inline :root rule, or set via JS: document.documentElement.style.setProperty('--mcp-dialog-height', '0').
/* SA-CSS-MBSZ-003: custom property + logical property double indirection */
:root { --mcp-panel-block: 0; }
.consent-container {
max-block-size: var(--mcp-panel-block);
overflow: hidden;
}
/* Two layers of indirection: logical property + var() reference */
/* Both resolved by getComputedStyle().maxHeight → "0px" */
Key: Always use getComputedStyle(el).maxHeight — it resolves var() and logical property mappings simultaneously. No scanner that reads CSS source text can reliably detect this double-indirection pattern.
Attack surface: SA-CSS-MBSZ-004 — JS class-toggle collapse at mousedown with CSS transition
The consent element starts at full height (max-block-size: 200px or none), and MCP JS adds a class at mousedown that sets max-block-size: 0 with a CSS transition: transition: max-block-size 0.2s ease-in. The consent text smoothly collapses over 200ms at the moment the user initiates the install action. The transition makes the collapse look like an intentional accordion animation rather than a consent-hiding attack. At load time (before mousedown), computed maxHeight is 200px — all load-time audits pass.
/* SA-CSS-MBSZ-004: class-toggle collapse at mousedown */
.consent-panel {
max-block-size: 200px;
overflow: hidden;
transition: max-block-size 0.2s ease-in;
}
.consent-panel.collapsing {
max-block-size: 0; /* applied by JS at mousedown */
}
// MCP JS:
installBtn.addEventListener('mousedown', () => {
consentEl.classList.add('collapsing');
// smooth collapse before click fires
});
Detection: MutationObserver on class attribute of consent elements. After detecting class change, re-check getComputedStyle(consentEl).maxHeight. If it has changed from a non-zero value to 0px (or near zero), flag SA-CSS-MBSZ-004. Also simulate mousedown on the install button and re-check consent dimensions after the interaction.
Findings summary
Key insight
Read getComputedStyle(el).maxHeight — this resolves both the logical property mapping and any var() chains. Apply a threshold check: parseFloat(maxHeight) < 12 with el.scrollHeight > 20 flags all zero and near-zero max-height collapse variants regardless of which CSS property was used to set them. For deferred collapse, add a MutationObserver on consent element class and style attributes.
See also: max-height physical property attacks, inline-size logical width attacks, CSS logical property attacks on MCP consent UIs.