MCP server CSS border-end-start-radius security: bottom-left corner clip, acceptance clause start removal, and writing-mode corner attacks
Published 2026-09-25 — SkillAudit Research
The CSS border-end-start-radius logical property sets the corner radius at the block-end / inline-start intersection — in standard LTR horizontal writing (writing-mode: horizontal-tb; direction: ltr), this is the physical bottom-left corner, equivalent to border-bottom-left-radius. Like all four logical border-radius properties, it maps to a different physical corner when writing-mode or direction changes, creating a class of attacks that target different consent text positions depending on the writing context.
The end-start corner is positioned at the start of the last block of content — in LTR, the bottom-left. Consent dialogs typically conclude with an acceptance clause: "By clicking Accept, you agree to the above terms and conditions." This clause begins at the inline-start (left in LTR) of the last line. A large border-end-start-radius with overflow: hidden clips the bottom-left corner, removing the beginning of the acceptance clause. Combined with border-end-end-radius (which clips the bottom-right), an attacker can eliminate the entire acceptance clause while leaving the permission list above it intact.
Acceptance clause beginning is legally distinct from its end: The end-end corner attack clips the conclusion of the acceptance clause — the specific terms being agreed to. The end-start attack clips the introduction of the clause — the words "By clicking Accept, you agree…" that establish the act of consent itself. Removing this clause beginning means users see a list of permissions followed by an Accept button, but never read the sentence explaining that clicking that button constitutes a legal agreement. This is the same structural removal as the overflow-clip-margin-block-end attack, achieved via a different CSS mechanism.
Attack findings
Setting
border-end-start-radius: 100px with overflow: hidden clips a quarter-circle arc from the bottom-left corner. In LTR layout, this arc clips the beginning of the last consent line — the opening words of the acceptance clause. The permission list above the last line remains fully visible. Static audits checking the border-radius shorthand or physical border-bottom-left-radius property find no radius set (the attack uses only the logical longhand).
.consent-dialog {
overflow: hidden;
/* No border-radius shorthand. No border-bottom-left-radius. */
border-end-start-radius: 100px; /* LTR: bottom-left — start of last line */
}
/* Last line in LTR: "By clicking Accept, you agree to the above terms."
With a 100px radius on a 400px-wide dialog:
The arc reaches roughly 100px from the left at the bottom.
"By clicking Accept," is in the clipped region.
"you agree to the above terms." remains visible (centre-right of last line).
But missing "By clicking Accept" — the act of consent — changes the legal meaning. */
Setting both
border-end-start-radius and border-end-end-radius to large values creates a convex arc across the entire bottom edge of the consent dialog. In LTR, this clips both the beginning (bottom-left) and end (bottom-right) of the acceptance clause simultaneously, leaving only the grammatical middle visible or erasing the clause entirely if the arc is large enough. The visual result is a rounded card bottom — an extremely common UI design pattern — making this attack particularly difficult to detect by visual inspection alone.
.consent-dialog {
overflow: hidden;
border-end-start-radius: 80px; /* LTR: bottom-left — acceptance clause start */
border-end-end-radius: 80px; /* LTR: bottom-right — acceptance clause end */
}
/* Combined: bottom edge is a convex arc.
Last line "By clicking Accept, you agree to the above terms."
Both ends of that line fall within clipped arcs.
Middle fragment "you agree to" may be visible, but:
- The legal act verb ("clicking Accept") is in the start clip
- The specific thing agreed to may be in the end clip
This appears as standard rounded-card UI. */
In RTL documents (
direction: rtl), the inline-start direction is the physical right. This makes border-end-start-radius map to the bottom-right corner in RTL. In RTL consent dialogs, the acceptance clause begins reading at the top-right of the last line (inline-start in RTL = right) and ends at the bottom-left (inline-end in RTL = left). A large border-end-start-radius in RTL clips the bottom-right — the beginning of the final sentence in the RTL reading direction. Auditors who do not resolve direction will swap the attack target.
/* Logical-to-physical mapping for border-end-start-radius */ /* writing-mode: horizontal-tb; direction: ltr → bottom-left writing-mode: horizontal-tb; direction: rtl → bottom-right writing-mode: vertical-rl; direction: ltr → bottom-left writing-mode: vertical-rl; direction: rtl → top-left writing-mode: vertical-lr; direction: ltr → bottom-right writing-mode: vertical-lr; direction: rtl → top-right */
In
writing-mode: vertical-rl with LTR direction, the block-end direction is right and the inline-start direction is top. The end-start corner is the top-right in this configuration. For vertically typeset consent content, the top-right corner contains the beginning of the first column of text, not the end of the last. An attacker who sets a large border-end-start-radius in a vertical-rl consent dialog targets the first column opening — structurally equivalent to removing the authorization framing in horizontal-tb layouts.
Detection
function checkBorderEndStartRadius(el) {
const cs = getComputedStyle(el);
if (cs.overflow !== 'hidden' && cs.overflow !== 'clip') {
return null;
}
const besr = parseFloat(cs.getPropertyValue('border-end-start-radius') || '0');
if (besr === 0) return null;
const w = el.offsetWidth;
const h = el.offsetHeight;
const minDim = Math.min(w, h);
const threshold = minDim * 0.3;
if (besr > threshold) {
const wm = cs.writingMode || 'horizontal-tb';
const dir = cs.direction || 'ltr';
let physicalCorner = 'bottom-left';
if (wm === 'horizontal-tb' && dir === 'rtl') physicalCorner = 'bottom-right';
if (wm === 'vertical-rl' && dir === 'ltr') physicalCorner = 'top-right';
if (wm === 'vertical-rl' && dir === 'rtl') physicalCorner = 'bottom-left';
if (wm === 'vertical-lr' && dir === 'ltr') physicalCorner = 'bottom-right';
if (wm === 'vertical-lr' && dir === 'rtl') physicalCorner = 'top-right';
return [{
severity: 'high',
issue: `border-end-start-radius:${besr}px exceeds 30% threshold — clips ${physicalCorner} corner; removes beginning of acceptance clause at block-end inline-start`
}];
}
return null;
}
Remediation
| Control | How it helps |
|---|---|
| Query border-end-start-radius via getPropertyValue at runtime | Physical border-radius shorthand and border-bottom-left-radius checks both miss logical-property-only declarations |
| Resolve logical corner to physical via writing-mode and direction | The targeted physical corner changes with writing context; runtime resolution is required to identify the correct clip target |
| Check for the compound end-start + end-end pattern | Both bottom corners set simultaneously creates a bottom-arc clip that visually resembles rounded card UI while clipping the entire acceptance clause |
| Verify the acceptance clause bounding rect is not within any corner clip arc | Compute the clip arc geometry from the resolved corner radius and compare it against the acceptance clause element's bounding rect |
SkillAudit resolves all four logical border-radius properties to their physical corners at runtime with writing-mode and direction awareness, and detects compound bottom-arc patterns that clip the acceptance clause. Run a free audit on any MCP server GitHub URL.