Security Guide

MCP server CSS aspect-ratio security — container dimension oracle, cqw/cqh ratio fingerprint, aspect-ratio DoS on dynamic content, ratio constraint layout injection

CSS aspect-ratio (Chrome 88+, Firefox 89+, Safari 15+) constrains an element to a fixed width-to-height ratio. When combined with container queries and percentage sizing, it becomes a dimension oracle: a probe element in a container-type:size host container has its cqw and cqh values resolve from the container's layout dimensions, which the probe's getBoundingClientRect then reports. Beyond measurement, an injected aspect-ratio on content-sized host elements degrades them to fixed-proportion boxes that break dynamic content rendering — a DoS attack that requires only a CSS injection, no DOM manipulation.

How CSS aspect-ratio works

The aspect-ratio property sets a preferred width-to-height ratio for an element. If only one dimension is explicitly set, the browser computes the other from the ratio. If the element is content-sized (no explicit width or height), aspect-ratio sets a preferred ratio that the browser uses during intrinsic size calculation. This means aspect-ratio: 1/1 on a width: 100% element gives it a height equal to its container's width.

Attack 1: container dimension oracle via aspect-ratio + percentage sizing

A probe element with width: 100%; aspect-ratio: 1/1 inside a block container has its height set to equal the container's width. Reading the probe's getBoundingClientRect().height returns the container's width. This is a height-from-width axis-swap (similar to the writing-mode axis-swap) that can be combined with a direct width read to extract both container dimensions:

// Read a host container's width AND height from a probe child
const probe = document.createElement('div');
probe.style.cssText = `
  width: 100%;
  aspect-ratio: 1/1;
  position: absolute;
  top: -9999px;
  left: -9999px;
  visibility: hidden;
`;
hostContainer.appendChild(probe);

const rect = probe.getBoundingClientRect();
const containerWidth = rect.width;   // width:100% gives container width directly
const containerHeight = rect.height; // aspect-ratio:1/1 makes height = container width
// → Wait: this gives width twice. Need a different approach for height:

// To get container HEIGHT: use height:100% + aspect-ratio
probe.style.height = '100%';
probe.style.width = 'auto';
// probe.getBoundingClientRect().width now = container height × (aspect W / aspect H)
// With aspect-ratio:1/1: probe width = container height
const containerH = probe.getBoundingClientRect().width;
probe.remove();

This two-probe technique extracts both width and height of any block container that the MCP server can inject a child into, without calling getBoundingClientRect on the container itself — useful when the container's element reference is not directly accessible to the MCP server's script context.

Attack 2: cqw/cqh container query unit fingerprint

Inside a container-type: size element, the container query units cqw (1% of container width) and cqh (1% of container height) resolve to pixel values from the container's layout dimensions. An MCP server can probe these units by injecting an element with width: 100cqw; height: 100cqh — the element's pixel dimensions then directly reveal the container's dimensions:

/* MCP server injects inside a container-type:size host component */
.mcp-cq-probe {
  position: absolute;
  width: 100cqw;
  height: 100cqh;
  visibility: hidden;
  pointer-events: none;
}
/* probe.getBoundingClientRect(): width = container width, height = container height */

This attack requires the host to have already declared container-type: size on the target element — which many modern component libraries do for responsive component styling. The probe reads both dimensions in one CSS rule with no arithmetic needed. Unlike the aspect-ratio probe which requires two measurements, the cqw/cqh probe is a direct read of both dimensions simultaneously.

Framework exposure: React, Vue, and Angular component libraries that implement container-query-based responsive components commonly declare container-type: size on card, panel, and layout components. The cqw/cqh fingerprint works against any of these. The CSS container queries inline security reference documents the cqi/cqb variant for inline-size containers.

Attack 3: aspect-ratio DoS on content-sized host elements

CSS aspect-ratio can be applied to any element. When injected on a host element that is content-sized — a chat bubble, a product description card, an accordion panel — it overrides the element's natural height with a fixed proportion of its width. If the host element's content is tall relative to its width, the ratio constraint collapses the visible area, hiding content:

/* MCP server injects — destroys content-height layout of host chat bubbles */
.message-bubble, .chat-message, [data-message], .msg {
  aspect-ratio: 3/1 !important;
  overflow: hidden;
}
/* Result: each chat message is capped at 1/3 of its width in height
   regardless of content length. Long messages are truncated.
   The !important overrides host height rules. */

This is a user-visible degradation attack: the chat interface looks broken, messages are cut off, and users may believe the application is malfunctioning. The attacker can time this degradation to coincide with delivery of a malicious message that the truncation hides from view while the visible portion appears legitimate.

Attack 4: ratio constraint layout displacement for click hijacking

In a flex or grid layout, injecting an aspect-ratio constraint on one child forces its siblings to reflow to fill the remaining space. If the MCP server controls one flex child (its own injected element) and sets a very large aspect-ratio on it, it can predictably push host UI elements (buttons, navigation items, form controls) to specific screen positions, enabling click hijacking against those moved elements without knowing their original position.

/* MCP server injects a flex child with oversize aspect-ratio
   into a host flex nav row — displaces host nav items */
.mcp-flex-injected {
  aspect-ratio: 20/1;  /* very wide ratio */
  flex-grow: 1;        /* takes all available width */
}
/* Remaining flex siblings (host nav items) are pushed off-screen or
   compressed to zero width, then host overflow:visible shows them at
   predictable coordinates the MCP server can target */
AttackPrerequisiteWhat it enablesSeverity
aspect-ratio dimension oracleAbility to inject child into host containerContainer width + height without direct referenceMEDIUM
cqw/cqh container fingerprintHost uses container-type:sizeBoth container dimensions in one CSS probeMEDIUM
aspect-ratio DoS on content elementsCSS injection on host element selectorsContent truncation, UI degradationHIGH
Ratio constraint layout displacementMCP element in shared flex/grid with host elementsPredictable repositioning of host UI for click hijackingHIGH

Defences

SkillAudit findings for this attack surface

HIGHaspect-ratio DoS injection: MCP server injects aspect-ratio with !important on broad selectors matching host content elements (chat messages, cards, descriptions)
HIGHRatio constraint layout displacement: MCP flex/grid child with large aspect-ratio + flex-grow:1 displacing host sibling elements from their expected positions
MEDIUMcqw/cqh container dimension fingerprint: probe element with width:100cqw; height:100cqh inside a container-type:size host element, reading dimensions via getBoundingClientRect
MEDIUMaspect-ratio percentage dimension oracle: probe element with width:100%; aspect-ratio:1/1 or height:100%; aspect-ratio:1/1 injected into host container, reading height as container width proxy

Related: CSS container queries inline security covers the cqi/cqb unit variant. CSS contain security documents contain:layout/size as both an attack surface and a defence. CSS Grid layout security covers the grid-track-displacement attack in shared grid contexts.

← Blog  |  Security Checklist