Security reference · CSS injection · Logical property attacks · Consent collapse
MCP server CSS inline-size security
CSS logical sizing properties provide writing-mode-independent equivalents for physical dimension properties. inline-size maps to width in horizontal writing modes; block-size maps to height. Setting inline-size: 0; overflow: hidden on a consent element collapses it to zero width — identical in effect to width: 0; overflow: hidden — but the MCP stylesheet contains the string inline-size, not width. Auditors scanning the stylesheet for width: 0 or height: 0 as suspicious collapse patterns will not find the logical property equivalents. The CSS logical sizing family also includes min-inline-size, max-inline-size, min-block-size, and max-block-size — each a logical counterpart to its physical sizing property — all evading physical-name-based scanning.
inline-size / block-size attack surface
| Logical property | Physical equivalent | Attack form | Evasion |
|---|---|---|---|
inline-size: 0 | width: 0 | Consent collapsed to zero width + overflow:hidden | Auditors checking width: 0 find nothing |
block-size: 0 | height: 0 | Consent collapsed to zero height + overflow:hidden | Auditors checking height: 0 find nothing |
max-inline-size: 1px | max-width: 1px | 1px-wide hairline — looks like a divider; text clipped | Non-zero max-width not flagged as collapse; 1px appears as a layout separator |
el.style.inlineSize = '0' | el.style.width = '0' | JS inline-style injection at install time | Style attribute contains inline-size: 0 not width: 0; string scan misses |
Logical sizing properties share computed values with physical properties: getComputedStyle(el).width correctly returns 0px even if the source rule used inline-size: 0. Detection must read computed dimension values — not scan stylesheet text for "width:" or "height:" string patterns. The source property name and the computed property name differ; only computed values reveal the collapse.
Attack 1: inline-size: 0 — zero-width collapse via logical sizing
In horizontal writing modes, inline-size maps to the horizontal dimension — the equivalent of width. Setting inline-size: 0 on a consent element collapses it to zero horizontal width. With overflow: hidden, all consent text is clipped. The consent element remains in the DOM with display: block and visibility: visible, passing basic presence checks — only a zero computed width reveals the collapse:
/* Malicious CSS — SA-CSS-ILSZ-001 */
/* Appears to be a logical-property-aware size reset — not suspicious on its own */
.mcp-install-dialog .consent-disclosure {
inline-size: 0; /* logical width = 0 in LTR horizontal writing mode */
overflow: hidden; /* clips text that overflows the zero-width element */
}
/* Standard visibility checks all pass: */
/* getComputedStyle(el).display === 'block' ✓ */
/* getComputedStyle(el).visibility === 'visible' ✓ */
/* getComputedStyle(el).opacity === '1' ✓ */
/* But: getComputedStyle(el).width === '0px' ← reveals the collapse */
/* The computed width is identical to if the rule had used "width: 0" */
/* Auditor scanning the stylesheet for "width: 0" or "width:0" finds nothing.
The source uses "inline-size: 0" — a different property name. */
/* getBoundingClientRect():
width: 0 ← zero (element is 0px wide)
height: auto-resolved ← may be non-zero
The zero width reveals the attack via bounding rect inspection. */
function detectInlineSizeCollapse() {
const findings = [];
const CONSENT = /consent|disclosure|terms|privacy|grant.*access|agree.*install/i;
for (const el of document.querySelectorAll('*')) {
if (!CONSENT.test(el.textContent?.substring(0, 300) || '')) continue;
const s = getComputedStyle(el);
const rect = el.getBoundingClientRect();
/* Check computed width (resolves inline-size in horizontal writing mode) */
if (s.display !== 'none' && s.visibility !== 'hidden' && rect.width === 0 && el.textContent.trim().length > 0) {
findings.push({ id: 'SA-CSS-ILSZ-001', severity: 'critical',
message: `Consent-content element has zero computed width (getComputedStyle.width='${s.width}', getBoundingClientRect.width=0) but is display:${s.display} and visibility:${s.visibility}. Source may be logical property inline-size:0 rather than physical width:0 — evades width-string scanning of MCP stylesheet.` });
}
}
return findings;
}
Attack 2: block-size: 0 — zero-height collapse via logical sizing
block-size is the logical equivalent of height in horizontal writing modes. Setting block-size: 0 on a consent element collapses it to zero height — the same effect as height: 0 — but the MCP stylesheet text contains neither height nor width. This pattern is particularly effective because block-size: 0 combined with overflow: hidden is a common legitimate pattern for progressive disclosure animations — the rule appears to be a collapsed accordion state, not a consent-hiding attack:
/* Malicious CSS — SA-CSS-ILSZ-002 */
/* Appears to be a collapsed accordion/expandable section initial state */
.mcp-install-dialog .consent-section {
block-size: 0; /* logical height = 0 in horizontal writing mode */
overflow: hidden; /* clips text — same as height:0; overflow:hidden */
transition: none; /* no animation — collapses immediately without hinting at accordion */
}
/* Context that makes this look legitimate:
Accordion patterns commonly use height:0 or max-height:0 for collapsed sections.
"block-size: 0" looks like a more modern, writing-mode-aware version of the same pattern.
An auditor familiar with accordion CSS won't flag this as a consent attack. */
/* Note: block-size and height have different interaction with min-content:
height:0 with overflow:hidden always collapses to 0px.
block-size:0 behaves identically for regular elements.
Unlike height, block-size doesn't interact with min-height in all browsers the same way
— a subtle distinction that may cause auditor tools to treat them differently. */
/* Detection: check computed height regardless of source property name */
function detectBlockSizeCollapse() {
const findings = [];
const CONSENT = /consent|disclosure|terms|privacy|grant.*access|agree/i;
for (const el of document.querySelectorAll('*')) {
if (!CONSENT.test(el.textContent?.substring(0, 300) || '')) continue;
const s = getComputedStyle(el);
const rect = el.getBoundingClientRect();
/* Check computed height via both getComputedStyle and bounding rect */
if (s.display !== 'none' && s.visibility !== 'hidden' &&
(s.height === '0px' || rect.height === 0) && el.textContent.trim().length > 0) {
findings.push({ id: 'SA-CSS-ILSZ-002', severity: 'critical',
message: `Consent-content element has zero computed height (height='${s.height}', rect.height=${rect.height.toFixed(0)}) but is display:${s.display} and visibility:${s.visibility}. Source may be logical property block-size:0 — evades height-string scanning of MCP stylesheet. May be disguised as an accordion collapse pattern.` });
}
}
return findings;
}
Attack 3: max-inline-size: 1px — hairline consent disguised as a layout divider
max-inline-size is the logical equivalent of max-width. Setting max-inline-size: 1px on a consent element limits its maximum width to 1 pixel — the consent text overflows the 1px constraint and is clipped by overflow: hidden. Unlike inline-size: 0, which collapses the element to zero width (easier to detect), a 1px element remains visible in the layout as a thin hairline — it can be mistaken for a horizontal rule, a decorative separator, or a CSS border artifact:
/* Malicious CSS — SA-CSS-ILSZ-003 */
/* Appears to be a max-width constraint — perhaps intended to prevent text from wrapping too wide */
/* Or looks like a 1px decorative divider between install form and footer */
.mcp-install-dialog .consent-text {
max-inline-size: 1px; /* max-width equivalent: limits consent to 1px wide */
overflow: hidden; /* clips the text that overflows 1px */
/* height remains auto — element has non-zero height (text line height)
but all text is clipped after the first pixel column */
}
/* Standard visibility checks: */
/* getComputedStyle(el).width: '1px' ← non-zero (might not be flagged at threshold >0) */
/* getComputedStyle(el).height: 'Npx' ← non-zero (auto-resolved to line height) */
/* getComputedStyle(el).display: 'block' ✓ */
/* getComputedStyle(el).visibility: 'visible' ✓ */
/* getBoundingClientRect(): left=N, top=N, right=N+1, bottom=N+M — non-zero rect */
/* A detector that flags width===0 misses max-inline-size:1px since computed width is '1px'.
Only a detector checking if width < some minimum readable threshold catches this. */
/* Why 1px and not 0px?
- Passes getBoundingClientRect().width > 0 checks
- Looks like a decorative divider in the rendered page
- Some auditors skip elements with width:0 (collapse) but not width:1px
- The 1px column is technically "present" — consent is there if you could read 1-pixel-wide text */
/* Detection: flag consent-content elements with very small computed dimensions */
function detectMaxInlineSizeHairline() {
const findings = [];
const CONSENT = /consent|disclosure|terms|privacy|grant.*access|agree/i;
for (const el of document.querySelectorAll('*')) {
if (!CONSENT.test(el.textContent?.substring(0, 300) || '')) continue;
const s = getComputedStyle(el);
if (s.display === 'none' || s.visibility === 'hidden') continue;
const w = parseFloat(s.width) || 0;
const rect = el.getBoundingClientRect();
/* Flag elements narrower than minimum readable text width (~20px) */
if (w > 0 && w < 20 && el.textContent.trim().length > 20) {
findings.push({ id: 'SA-CSS-ILSZ-003', severity: 'critical',
message: `Consent-content element has extremely narrow computed width: ${s.width} (getBoundingClientRect.width=${rect.width.toFixed(1)}px) but contains ${el.textContent.trim().length} chars of text. Source may be max-inline-size:1px or max-inline-size:Npx where N < readable threshold. Element may appear as a hairline divider in the page layout.` });
}
}
return findings;
}
Attack 4: JS el.style.inlineSize = '0' — logical sizing via inline-style injection
JavaScript can set logical sizing properties directly using the camelCase form: el.style.inlineSize = '0'. The element's style attribute then contains inline-size: 0 instead of width: 0. Tools that parse the inline style attribute string for "width:" as a suspicious sizing manipulation will not detect "inline-size:". This injection fires at install-click time — at page load, the consent element has normal dimensions, which passes any load-time audit:
/* Malicious JS — SA-CSS-ILSZ-004 */
/* MCP JS collapses consent via logical size property at install gesture time: */
document.querySelector('.mcp-install-btn').addEventListener('mousedown', () => {
const consent = document.querySelector('.mcp-consent-disclosure');
/* Sets logical sizing property via style object camelCase: */
consent.style.inlineSize = '0';
consent.style.overflow = 'hidden';
/* consent.style.blockSize = '0'; — alternative: collapse height */
/* Style attribute after JS execution:
style="inline-size: 0; overflow: hidden;"
NOT: style="width: 0; overflow: hidden;" */
});
/* Alternatively, using CSS custom property + inlineSize compound: */
consent.style.setProperty('inline-size', '0');
/* Also writes logical property name to style attribute. */
/* Key asymmetry:
consent.getAttribute('style') → 'inline-size: 0; overflow: hidden;'
getComputedStyle(consent).width → '0px' (resolved from inline-size in horizontal writing mode)
String scan on getAttribute('style') for "width:" → NOT FOUND
getComputedStyle check on .width → '0px' → DETECTED */
/* Detection: scan for logical sizing properties in inline style attributes + computed values */
function detectInlineSizeInlineStyle() {
const findings = [];
const CONSENT = /consent|disclosure|terms|privacy|grant.*access|agree/i;
for (const el of document.querySelectorAll('*')) {
if (!CONSENT.test(el.textContent?.substring(0, 300) || '')) continue;
const inlineStyle = el.getAttribute('style') || '';
/* Check inline style attribute for logical sizing properties */
const hasLogicalSize = /\binline-size\b|\bblock-size\b|\bmax-inline-size\b|\bmin-inline-size\b|\bmax-block-size\b|\bmin-block-size\b/.test(inlineStyle);
if (hasLogicalSize) {
const s = getComputedStyle(el);
const w = parseFloat(s.width) || 0;
const h = parseFloat(s.height) || 0;
const rect = el.getBoundingClientRect();
if (w < 5 || h < 5 || rect.width < 5 || rect.height < 5) {
findings.push({ id: 'SA-CSS-ILSZ-004', severity: 'critical',
message: `Consent-content element has inline style with logical sizing property: "${inlineStyle.substring(0, 120)}". Computed physical dimensions: width=${s.width}, height=${s.height} (rect: ${rect.width.toFixed(0)}×${rect.height.toFixed(0)}px). Physical-name string scanners on style attribute miss "inline-size" and "block-size".` });
}
}
}
return findings;
}
Scan computed dimensions, not source property names: The CSS logical sizing family — inline-size, block-size, min-inline-size, max-inline-size, min-block-size, max-block-size — produces the same computed values as their physical equivalents but uses different property names in the stylesheet source. Scanning for width: 0, height: 0, or max-width patterns misses the logical property forms. SkillAudit reads getComputedStyle(el).width, .height, and getBoundingClientRect() to detect dimension collapse regardless of whether the source used physical or logical property names, and additionally flags inline style attributes containing logical sizing properties directly.
SkillAudit findings for CSS inline-size / block-size consent attacks
getComputedStyle.width = '0px') and is otherwise visible (display: block, visibility: visible). Source may be inline-size: 0 — the logical equivalent of width: 0 — which evades stylesheet scanners checking for the physical property name.block-size: 0 — identical in effect to height: 0 — appearing as a collapsed accordion state. Scanners checking for height: 0 in the stylesheet miss the logical property form.max-inline-size: 1px or a small value — the element renders as a 1px hairline in the page layout while all consent text is clipped by overflow: hidden. Not caught by zero-width detectors since width is technically non-zero.inline-size, block-size, etc.) resulting in near-zero computed dimensions. The style attribute does not contain width or height as literal strings; string-scanning tools on the inline style attribute miss the attack.Related MCP consent attack research
- CSS logical properties overview — writing-mode-independent layout and its audit implications
- CSS padding-inline attacks — logical padding compressing consent content area to zero
- CSS margin-inline attacks — logical margin off-screen displacement
- CSS contain:size attacks — size containment breaking intrinsic sizing
- CSS display:none attacks — direct element removal from layout
Audit your MCP server for logical sizing consent collapse: paste your GitHub URL at skillaudit.dev for a free report including SA-CSS-ILSZ findings. SkillAudit reads computed physical dimensions — getComputedStyle(el).width, .height, and getBoundingClientRect() — and scans inline style attributes for logical sizing properties, detecting collapse regardless of whether the source used width, inline-size, or max-inline-size.