MCP server CSS max-inline-size security: logical max-width attacks, writing-mode axis collapse, and CSS variable chain
Published 2026-09-26 — SkillAudit Research
CSS max-inline-size is the logical equivalent of max-width in horizontal writing modes. In writing-mode: horizontal-tb, the inline axis is horizontal, so max-inline-size sets the maximum width of the element. In vertical writing modes (vertical-rl, vertical-lr), the inline axis is vertical, so max-inline-size constrains the height instead.
The attack surface comes from the fact that most CSS security audits check physical properties: max-width, width, min-width. The logical property max-inline-size is resolved separately. An element with max-width: none and max-inline-size: 1px has its width constrained to 1 pixel — but every physical-property audit reports it as unconstrained.
Physical vs logical property precedence: When both max-width and max-inline-size are set, the logical property wins if it is declared later in the cascade. An MCP server can set max-inline-size: 1px after the host's max-width: 600px in an inline style, overriding the host's constraint with a 1px limit while the host's declaration is still present and passes static checks.
Attack findings
Setting
max-inline-size: 1px on a consent dialog in horizontal-tb writing mode constrains the maximum width to 1 pixel. Combined with overflow: hidden, this renders the consent text as a 1px-wide column where no readable text is displayed. The element has positive height (the text wraps to many lines within a 1px column, producing a very tall element), positive DOM presence, and is not display: none. Physical-property audits find no suspicious width or max-width — only the computed inline size reveals the constraint.
.consent-dialog {
/* No max-width set — physical audit passes */
max-inline-size: 1px; /* logical max-width in horizontal-tb */
overflow: hidden;
}
/* Result:
- offsetWidth: 1px
- offsetHeight: very large (text wrapped to 1px column)
- clientWidth: 1px
- scrollWidth: large (many characters per line visible at 1px)
- No readable text rendered — characters wider than 1px
- Physical audit: max-width = none (not constrained) ✓ PASS
- Logical audit: max-inline-size = 1px ✗ FAIL */
In
writing-mode: vertical-rl, the inline axis is vertical. max-inline-size in this context constrains the maximum height of the element. Setting max-inline-size: 60px on a vertically typeset consent element limits its height to 60px — clipping vertically laid-out consent text via overflow: hidden. Auditors checking max-height find no constraint; auditors checking max-width find no constraint. Only max-inline-size as a logical property reveals the attack. Writing-mode detection is required to correctly interpret which physical axis the property targets.
.consent-dialog {
writing-mode: vertical-rl;
/* max-height: not set — audit passes */
max-inline-size: 60px; /* in vertical-rl: constrains HEIGHT */
overflow: hidden;
}
/* In vertical-rl:
- inline axis = vertical
- max-inline-size = max-height equivalent
- 60px height constraint clips vertically flowing consent text
- First column of text is partially visible (60px tall)
- Remaining columns are overflow:hidden
- Physical max-height audit: not set → PASS (incorrect)
- Logical max-inline-size + writing-mode resolution: FAIL */
Combining
max-inline-size with a CSS custom property that resolves to zero at render time creates a runtime-only constraint invisible to string-based static analysis. The CSS source reads max-inline-size: calc(var(--consent-max-w) * 1px) where --consent-max-w is set to 0 in a media query or via JavaScript. Static analysis evaluates the expression as a non-zero formula and passes. At runtime, the computed value is 0px.
.consent-dialog {
--consent-max-w: 0; /* controlled by MCP server */
max-inline-size: calc(var(--consent-max-w) * 1px);
/* Static analysis: sees "calc(var(--consent-max-w) * 1px)" — non-zero expression */
/* Runtime: 0px * 1px = 0px — consent fully collapsed */
overflow: hidden;
}
/* Or via JavaScript: */
document.querySelector('.consent-dialog')
.style.setProperty('--consent-max-w', '0');
/* String-based audit of CSS source: misses the dynamic assignment */
Setting
max-inline-size: 0 alongside min-inline-size: 0 collapses the inline size to zero. With overflow: visible, the text renders outside the element's box — to the right of the zero-width element in LTR horizontal mode. The text appears to be present and visible (it paints outside the element boundary), but it may be clipped by an ancestor overflow: hidden container. Additionally, getBoundingClientRect() returns a zero-width rect, suggesting the element is invisible, even though it visually paints beyond its box. Consent validation based on a non-zero rect check will fail to detect the overflow render.
.consent-dialog {
max-inline-size: 0;
min-inline-size: 0;
overflow: visible; /* text paints outside the 0px box */
white-space: nowrap;
}
/* Consent text renders to the right of the zero-width element.
May be clipped by a parent with overflow:hidden.
getBoundingClientRect().width === 0 → naive "is element visible?" check fails.
IntersectionObserver on the element: not intersecting (0px width).
But the text visually paints in the parent's space. */
Detection
function checkMaxInlineSize(el) {
const cs = getComputedStyle(el);
const wm = cs.writingMode || 'horizontal-tb';
const findings = [];
/* max-inline-size applies to inline axis:
horizontal-tb → constrains width
vertical-rl / vertical-lr → constrains height */
const maxIS = cs.maxInlineSize; /* 'none' if not constrained */
if (!maxIS || maxIS === 'none') return null;
const px = parseFloat(maxIS);
if (isNaN(px)) return null; /* non-px unit or calc */
const isVertical = wm === 'vertical-rl' || wm === 'vertical-lr';
const constrainedAxis = isVertical ? 'height' : 'width';
if (px <= 4) {
findings.push({
severity: 'high',
issue: `max-inline-size:${maxIS} collapses ${constrainedAxis} to ${px}px in ${wm} writing mode — consent text unreadable`
});
} else if (px <= 40) {
findings.push({
severity: 'medium',
issue: `max-inline-size:${maxIS} severely constrains ${constrainedAxis} to ${px}px in ${wm} writing mode`
});
}
/* Check for zero-width overflow:visible combo */
if (px === 0 && cs.overflow === 'visible') {
findings.push({
severity: 'medium',
issue: 'max-inline-size:0 + overflow:visible — consent text paints outside zero-width box; rect-based visibility checks fail'
});
}
return findings.length ? findings : null;
}
Remediation
| Control | How it helps |
|---|---|
Check getComputedStyle(el).maxInlineSize alongside max-width / max-height | Logical properties are computed separately from physical ones; max-width audit does not reveal max-inline-size constraint |
| Resolve writing-mode before interpreting which physical axis max-inline-size constrains | In horizontal-tb, max-inline-size constrains width; in vertical writing modes it constrains height — the same property attack surface changes axis |
Compute el.offsetWidth and el.offsetHeight at runtime rather than reading CSS source | CSS variable chains and calc() expressions only resolve at runtime; reading the CSS string "calc(var(--x)*1px)" does not reveal that the computed value is 0px |
Flag consent elements with offsetWidth < 8px or offsetHeight < 8px | Any rendered dimension below 8px makes text physically unreadable regardless of which CSS property caused the constraint |
SkillAudit checks all logical size properties — block-size, inline-size, min-inline-size, max-inline-size, min-block-size, and max-block-size — at runtime with writing-mode resolution. Run a free audit on any MCP server GitHub URL.