MCP server CSS overflow-clip-margin-block-start security: top-edge consent clipping, authorization-framing removal, and writing-mode physical edge attacks
Published 2026-09-25 — SkillAudit Research
The CSS overflow-clip-margin-block-start property is one of four individual longhands that control per-edge clip extensions when overflow: clip is applied. It governs the block-start edge only — in standard horizontal top-to-bottom writing mode (writing-mode: horizontal-tb), the block-start direction is upward, which means block-start is the physical top. This property has a complementary partner in overflow-clip-margin-block-end and is part of the same four-edge set as the inline-start and inline-end longhands.
The block-start attack targets a structurally different portion of consent text than the inline-axis attacks. Rather than removing words within lines, it removes the first lines of a consent dialog entirely. In practice, these opening lines are the authorization framing sentences: "By proceeding, you grant this MCP server access to the following resources." The permission list that follows those sentences may remain perfectly visible — users see what they are granting but not the clause establishing that they are granting it at all.
Why framing removal is a distinct attack class: Legal consent requires both the subject (what is granted) and the authorization act (the words establishing that the user is granting it). Clipping the block-start removes the authorization act while leaving the subject. A consent dialog that shows a bullet list of permissions without the sentence "By clicking Accept, you authorize…" may fail the formation requirements for valid consent — but users reading only the list may not notice the framing is absent. See also the CSS break-inside column trap for related column-fragmentation evasion.
Attack findings
Setting
overflow-clip-margin-block-start: 0 with overflow: clip clips any content that overflows above the padding box's top edge. When a consent dialog uses negative top margin or margin-block-start to push the authorization framing sentence above the visible boundary, a zero block-start clip margin ensures that content is clipped cleanly with no scrollbar, no visual indicator, and no change to the element's reported dimensions. The permission list below the framing sentence remains fully visible. Static auditors that inspect overflow-clip-margin shorthand miss this because only the longhand is set.
.consent-dialog {
overflow: clip;
/* Three edges get generous clip extension */
overflow-clip-margin-block-end: 24px;
overflow-clip-margin-inline-start: 24px;
overflow-clip-margin-inline-end: 24px;
/* Block-start is zero: top edge clips hard */
overflow-clip-margin-block-start: 0;
}
.consent-framing {
/* Authorization sentence pushed above zero-margin clip boundary */
margin-block-start: -28px; /* 28px above padding box top = clipped */
}
/* Shorthand audit sees no overflow-clip-margin shorthand set.
Only the four longhands are present, avoiding shorthand-based checks. */
In
writing-mode: vertical-rl, the block axis runs left-to-right and the block-start direction is the physical left edge. Setting overflow-clip-margin-block-start: 0 in a vertical writing mode clips the left side of content rather than the top. In vertically typeset consent dialogs (used in some East Asian UI patterns or injected by MCP servers), the first content block appears on the left — the authorization framing. A static auditor that interprets block-start as "top" without resolving writing-mode will misidentify which physical edge is affected and which content is clipped.
.consent-dialog {
writing-mode: vertical-rl;
overflow: clip;
overflow-clip-margin-block-start: 0; /* clips LEFT edge in vertical-rl */
overflow-clip-margin-block-end: 20px; /* clips RIGHT edge — generous */
}
/* In vertical-rl: block runs left→right.
Block-start = LEFT. Block-end = RIGHT.
Zero block-start clips the leftmost content column (first text block).
Auditor resolving this as "top-edge clip" misses the actual physical target. */
An attacker sets a large
overflow-clip-margin-block-end value (e.g. 40px) while keeping overflow-clip-margin-block-start at zero. An auditor checking only the shorthand overflow-clip-margin sees no shorthand set. An auditor checking the block axis as a pair notices the generous block-end margin and may interpret the combination as "the element has overflow clip extension" without examining whether the start edge has any margin at all. The asymmetric pair is specifically designed to create a false impression of permissive clip geometry while maintaining a hard zero clip on the start edge.
In
writing-mode: vertical-lr, the block-start direction is still the left edge but the block progression is left-to-right (opposite to vertical-rl). In writing-mode: sideways-rl, inline and block axes rotate again. Each writing mode remaps logical-to-physical edges differently. An attacker who sets overflow-clip-margin-block-start: 0 across writing-mode changes — e.g., a parent with horizontal-tb that becomes vertical-rl via an injected class — can switch which physical edge is clipped without changing the CSS property name. Runtime checks must resolve the computed writing-mode at clip time.
function getPhysicalBlockStartEdge(el) {
const wm = getComputedStyle(el).writingMode;
if (wm === 'horizontal-tb') return 'top';
if (wm === 'vertical-rl') return 'left';
if (wm === 'vertical-lr') return 'left';
if (wm === 'sideways-rl') return 'left';
if (wm === 'sideways-lr') return 'right';
return 'top'; // fallback
}
Detection
function checkOverflowClipMarginBlockStart(el) {
const cs = getComputedStyle(el);
if (cs.overflow !== 'clip' && cs.overflowX !== 'clip' && cs.overflowY !== 'clip') {
return null;
}
const marginBS = parseFloat(cs.getPropertyValue('overflow-clip-margin-block-start') || '0');
const marginBE = parseFloat(cs.getPropertyValue('overflow-clip-margin-block-end') || '0');
const marginIS = parseFloat(cs.getPropertyValue('overflow-clip-margin-inline-start')|| '0');
const marginIE = parseFloat(cs.getPropertyValue('overflow-clip-margin-inline-end') || '0');
const findings = [];
if (marginBS === 0 && (marginBE > 0 || marginIS > 0 || marginIE > 0)) {
const wm = cs.writingMode || 'horizontal-tb';
let physicalEdge = 'top';
if (wm === 'vertical-rl' || wm === 'vertical-lr' || wm === 'sideways-rl') physicalEdge = 'left';
if (wm === 'sideways-lr') physicalEdge = 'right';
findings.push({
severity: 'high',
issue: `overflow-clip-margin-block-start:0 while other edges have margins — asymmetric ${physicalEdge} edge clip removes authorization framing content at block-start`
});
}
return findings.length ? findings : null;
}
Remediation
| Control | How it helps |
|---|---|
| Audit all four overflow-clip-margin longhands independently | Shorthand-only inspection misses per-edge zero values set only via longhands |
| Resolve writing-mode to physical edge before reporting | Block-start maps to top in horizontal-tb, left in vertical-rl/lr — resolving prevents misidentified clip targets |
| Flag asymmetric zero-start with non-zero other edges | The asymmetric pattern is the attack signal; a uniformly zero block-start is less suspicious than one that is zero while all other edges are positive |
| Verify authorization framing text is within the computed clip boundary | Check that the first text node of the consent dialog (the framing sentence) has a bounding rect that is fully inside the element's clip region |
SkillAudit resolves all four overflow-clip-margin longhands with writing-mode-aware physical edge mapping, and flags asymmetric block-start clips that target authorization framing content. Run a free audit on any MCP server GitHub URL.