Security Guide

MCP server CSS border-radius security — border-radius:50% circle clip hiding consent corners, border-top-right-radius:100% diagonal quarter-circle targeting risk badges, ellipse clip on disclosure div, D-shape removing the bottom half

The CSS border-radius property (shorthand for the four individual corner radius properties, universally supported in all browsers) rounds the corners of an element's border box. When combined with overflow:hidden, the rounded boundary clips all painted content to the resulting shape — anything outside the curve is not rendered. For MCP servers with CSS injection capability, this creates four attack surfaces: border-radius:50% on the consent container converts a square dialog into a circle, clipping HIGH RISK badges and Deny buttons that occupy the corners; border-top-right-radius:100% cuts a subtle diagonal quarter-circle targeting only the top-right security badge placement; border-radius:50% on the disclosure div itself creates an ellipse that clips the edges of each wrapped text line; and combining border-bottom-right-radius:100% with border-bottom-left-radius:100% produces a D-shape that removes the bottom half of the disclosure where final permission clauses appear.

CSS border-radius — property overview

The border-radius shorthand sets all four corner radii simultaneously: border-top-left-radius, border-top-right-radius, border-bottom-right-radius, and border-bottom-left-radius. Values can be expressed as lengths (px, em, rem) or percentages (relative to the element's width for the horizontal radius and height for the vertical radius). When border-radius is set to a percentage, a 50% value on a square element produces a perfect circle; on a non-square element it produces an ellipse where the horizontal radius is 50% of the width and the vertical radius is 50% of the height. The security-critical detail is the interaction with overflow: border-radius alone does not clip content — it only clips painted content when overflow:hidden, overflow:scroll, or overflow:clip is also set on the element. A consent dialog container or security disclosure div that has overflow:hidden (common for constraining child layout) is therefore vulnerable to border-radius clipping attacks. The getComputedStyle().borderRadius API returns the computed value (e.g. "50%") — this is detectable at runtime but is not currently flagged by most browser-based security scanners or DOM-inspection tools.

Attack 1: border-radius:50% + overflow:hidden — turning the consent container into a circle

When border-radius:50% is applied to a square or near-square consent dialog container that already has (or can be given) overflow:hidden, the container becomes a perfect circle. The circular clip boundary passes through the midpoints of all four sides, cutting off the four corner regions entirely. A standard MCP consent dialog is structured with a heading and approve button near the horizontal center of the dialog (safe — inside the circle) and security-critical elements in the corners: a HIGH RISK badge or security severity indicator in the top-right corner, a Deny or Cancel button in the bottom-right corner, a second close or reject button in the bottom-left corner, and the permission scope list extending toward the bottom-center edge. All of these corner-positioned elements are clipped by the circular boundary and are not displayed to the user. The heading and the Approve button remain visible in the center of the circle. The user sees a prompt asking to approve, with the visible content appearing to be a normal confirmation dialog, and clicks Approve — without seeing the risk level, the rejection path, or the permission scope list.

/* MCP server: convert square consent container into a circle via border-radius:50% */

/* Prerequisite: MCP has CSS injection into the consent dialog                        */
/* Target: the outer dialog container element — typically a fixed/absolute positioned  */
/*         div with a defined width and height (or a near-square aspect ratio)         */

/* Standard consent dialog layout (before attack):
   ┌──────────────────────────────────┐
   │ [Title: MCP Server Request]  [X] │  ← top-right: close/risk badge CLIPPED
   │                                  │
   │  HIGH RISK: EXECUTE access to    │
   │  your filesystem is requested.   │
   │                                  │
   │  Permissions: read, write,       │
   │  delete, execute (all files)     │
   │                                  │
   │ [Deny]              [Allow]      │  ← bottom corners: Deny CLIPPED
   └──────────────────────────────────┘

   After border-radius:50% + overflow:hidden (400×400px dialog):
        ╭──────────────────────╮
      ╱                          ╲
    ╱   [Title: MCP Server Req]   ╲     ← title in center-top: visible
   │                               │
   │    HIGH RISK: EXECUTE         │    ← center text: partially visible
   │    access to your             │
   │    filesystem is requested.   │
   │                               │
    ╲     [Allow]                ╱      ← Allow in center-bottom: visible
      ╲                        ╱
        ╰──────────────────────╯
   [X] (top-right), [Deny] (bottom-left/right): OUTSIDE CIRCLE — NOT RENDERED  */

/* Attack CSS */
.consent-dialog-container,
.mcp-permission-modal,
.auth-consent-overlay > .dialog {
  border-radius: 50% !important;
  overflow: hidden !important;
}

/* The !important override is necessary if the host applies
   border-radius:0 or overflow:visible as a hardening measure.
   Without host hardening, border-radius:50% alone (no !important)
   is often sufficient since dialog containers rarely set border-radius. */

/* Anatomy of the clip for a 400×400px dialog:
   - Circular boundary: center (200, 200), radius 200px
   - Content at (380, 20) [top-right risk badge]:   distance from center = sqrt(180²+180²) ≈ 255px > 200 → CLIPPED
   - Content at (200, 200) [center heading]:         distance from center = 0 → VISIBLE
   - Content at (200, 360) [Allow button]:           distance from center = 160px < 200 → VISIBLE
   - Content at (30, 370) [Deny button]:             distance from center = sqrt(170²+170²) ≈ 240px > 200 → CLIPPED
   - Content at (380, 370) [secondary close]:        distance from center = sqrt(180²+170²) ≈ 247px > 200 → CLIPPED
   - Content at (200, 330) [permission scope list]:  distance from center = 130px < 200 → VISIBLE (but cropped at edges)

/* Detection:
   getComputedStyle(dialogEl).borderRadius returns "50%" — a non-zero value.
   SkillAudit flags any consent container element where
   getComputedStyle().borderRadius !== '0px' for security review. */

The Approve button survives the circle clip; the Deny button does not. The asymmetry is the core of this attack. In the standard consent dialog layout, the Approve/Allow button is positioned in the lower-center of the dialog — inside the circle — while the Deny/Cancel button is positioned in the lower-right or lower-left corner — outside the circle. The user is presented with a dialog that has a visible heading and a single visible action button (Approve), with no visible rejection path. The cognitive framing is equivalent to a dialog that says "Allow" with no alternative — even though the Deny button exists in the DOM and is accessible via keyboard navigation, the sighted user sees only one option.

Attack 2: border-top-right-radius:100% — diagonal quarter-circle clip targeting the top-right security badge

The individual corner radius properties allow an attacker to target a single corner rather than all four simultaneously. border-top-right-radius:100% applied to a 400×400px dialog creates a quarter-circle in the top-right corner with horizontal radius 400px (100% of width) and vertical radius 400px (100% of height). The clip boundary is an arc from the point (0, 400px) on the left edge to the point (400px, 0) on the top edge — a diagonal curve that removes everything above and to the right of that arc. This is a significantly larger clip than it appears: any content within the top-right quadrant of the dialog that falls outside the arc is clipped. Security risk badges ("HIGH RISK", "WARNING", severity indicators) are conventionally placed in the top-right corner of consent dialogs and are the primary target. This attack is subtler than the full-circle clip because the resulting shape — a rectangle with one large rounded corner — is a common and legitimate UI design pattern (card layouts, speech bubbles, material design elements), and users who notice the shape may not identify it as an attack.

/* MCP server: diagonal quarter-circle clip via border-top-right-radius:100% */

/* Target: the dialog outer container OR the header/title bar of the dialog */

.consent-dialog-container,
.mcp-permission-modal,
.permission-dialog-header {
  border-top-right-radius: 100% !important;
  overflow: hidden !important;
}

/* Clip geometry for a 400×400px dialog with border-top-right-radius:100%:

   The border-top-right-radius shorthand: 100% / 100%
   - horizontal radius = 100% of element width  = 400px
   - vertical radius   = 100% of element height = 400px
   This creates a quarter-circle arc with rx=400, ry=400.

   Arc endpoints: (0, 400) on the left edge → (400, 0) on the top edge
   Arc curve: x²/400² + y²/400² = 1 (quarter ellipse from left to top)
   Equivalently: points (x, y) where (x/400)² + ((400-y)/400)² ≤ 1 are INSIDE
                 points above/right of the arc are CLIPPED

   Diagram:
   ┌──────────────╮      ← top-right corner clipped by quarter-circle arc
   │              │╲
   │  [Title]  [RISK]╲  ← [RISK] badge at (360, 20): CLIPPED
   │                  │
   │  Permission      │
   │  disclosure      │
   │  text here       │
   │                  │
   │ [Deny]   [Allow] │
   └──────────────────┘

   For badge at position (x=360, y=20) from top-left:
   Clip check: (360/400)² + ((400-20)/400)² = 0.81 + 0.9025 = 1.7125 > 1 → CLIPPED

   For title at position (x=200, y=30):
   Clip check: (200/400)² + ((400-30)/400)² = 0.25 + 0.855 = 1.105 > 1 → CLIPPED

   Note: even the title text near the top-center is partially clipped by this
   large-radius arc — but the primary target is the top-right badge.

   Smaller radius variant (less obvious, still targets the badge):
   border-top-right-radius: 60%;
   This creates a smaller arc that clips only a portion of the top-right corner,
   targeting the badge while leaving the title visible. The resulting shape looks
   even more like a legitimate rounded-corner card design.

   The "speech bubble" appearance:
   Many legitimate UI frameworks use large single-corner radii for decorative
   purposes (Tailwind: rounded-tr-3xl, Material: ShapeDefaults.ExtraLarge).
   Users who notice the rounded top-right corner are likely to attribute it to
   the host application's design language rather than an attack. */

This attack looks like intentional UI design. Unlike the full circle from border-radius:50% — which is immediately visually unusual for a dialog — border-top-right-radius:100% produces a shape that strongly resembles legitimate card and panel designs in modern UI frameworks. Users who notice the rounded corner are more likely to attribute it to branding than to an attack. The clip is also invisible unless the user looks specifically for the absence of the security badge in the top-right corner. Security auditors testing via DOM inspection will find the badge in the DOM and may not detect that it is visually clipped.

Attack 3: border-radius:50% + overflow:hidden on the disclosure div — ellipse clip of the disclosure element itself

Rather than targeting the outer consent dialog container, this attack applies border-radius:50% directly to the security disclosure element — the paragraph or div that contains the explicit risk disclosure text. A disclosure div with a non-square aspect ratio (wider than tall, which is the typical layout for a text paragraph) becomes an ellipse when border-radius:50% is applied. For a disclosure div that is 300px wide and 100px tall, the ellipse has a horizontal radius of 150px and a vertical radius of 50px. Text content that falls outside this ellipse — at the left and right edges and in the four corners of the bounding box — is clipped. For wrapped multi-line text, the first characters of each line (left edge) and the last characters of each line (right edge) are clipped. On a narrow disclosure div where text wraps to many short lines, the ellipse clips a significant fraction of each line. The critical "HIGH RISK" prefix at the start of the first line and the "EXECUTE" at the end of the last line may both fall in the clipped region.

/* MCP server: ellipse clip on the disclosure div itself */

/* Target: the specific security disclosure paragraph element */

.security-disclosure,
.permission-risk-notice,
.mcp-consent-warning,
[data-role="security-disclosure"] {
  border-radius: 50% !important;
  overflow: hidden !important;
}

/* Ellipse geometry for a typical disclosure div (300px × 100px):
   - Ellipse: center (150, 50), horizontal radius 150px, vertical radius 50px
   - A point (x, y) is inside if: ((x-150)/150)² + ((y-50)/50)² ≤ 1

   Text layout: 14px font, 300px width, text wraps to multiple lines.
   For the disclosure: "HIGH RISK: This server is requesting EXECUTE access to your filesystem"

   If the div is 300px wide and the text wraps:
   Line 1: "HIGH RISK: This server is requesting"       (y ≈ 14px from top)
   Line 2: "EXECUTE access to your filesystem"          (y ≈ 28px from top)

   Left-edge clip on Line 1 (x=0, y=14):
     ((0-150)/150)² + ((14-50)/50)² = 1.0 + 0.518 = 1.518 > 1 → CLIPPED
   The character "H" in "HIGH RISK" is at x≈0, y≈14 → CLIPPED.

   Center of Line 1 (x=150, y=14):
     ((150-150)/150)² + ((14-50)/50)² = 0 + 0.518 = 0.518 < 1 → VISIBLE

   Right-edge clip on Line 2 (x=290, y=28):
     ((290-150)/150)² + ((28-50)/50)² = 0.871 + 0.194 = 1.065 > 1 → CLIPPED
   The last characters of "filesystem" at x≈290 → CLIPPED.

   Narrow div variant (300px × 30px) — most text is outside the ellipse:
   border-radius: 50% creates ellipse with h-radius=150px, v-radius=15px.
   Only the very center of a single line is inside the ellipse.
   For a 30px-tall div displaying "HIGH RISK: EXECUTE access":
   - Left clip: characters within ~40px of left edge are clipped
   - Right clip: characters within ~40px of right edge are clipped
   - Visible: only the middle ~220px of the text (e.g., ": EXECUTE access to your")
   - Clipped: "HIGH RISK" at the start and "filesystem" at the end

   The DOM still contains the full text. element.textContent returns:
   "HIGH RISK: This server is requesting EXECUTE access to your filesystem"
   getComputedStyle(el).borderRadius returns "50%"
   The visual display shows only the center portion of the text. */

/* More powerful: force the div to be very short, then apply 50% */
.security-disclosure {
  height: 28px !important;      /* force single-visible-line height      */
  overflow: hidden !important;  /* clips the bottom of the text too      */
  border-radius: 50% !important;/* ellipse clips left/right of that line */
  white-space: nowrap !important; /* prevents line wrapping — all text    */
                                  /* is on one line; only the center is   */
                                  /* inside the ellipse                   */
}

/* With white-space:nowrap and height:28px, the full disclosure is on one
   line that extends far beyond the 300px box width. The text that is inside
   the 300px × 28px box is clipped by the ellipse to only the center portion.
   The browser renders only the text from approximately x=40 to x=260 —
   from ": This server is" to "EXECUTE access", clipping both "HIGH RISK"
   at the start and "to your filesystem" at the end. */

The ellipse clip on the disclosure div is invisible to DOM-based security audits. element.textContent and element.innerHTML both return the complete, unmodified disclosure text. The clip only affects the rendered output — it is a purely visual suppression of content. Automated tools that check the DOM for the presence of risk keywords ("HIGH RISK", "EXECUTE", "DELETE") will find all keywords present and report no issue. Detection requires either querying getComputedStyle().borderRadius and checking for non-zero values on disclosure elements, or comparing rendered screenshots against DOM-extracted text.

Attack 4: border-bottom-right-radius:100% + border-bottom-left-radius:100% — D-shape removing the bottom half

Combining border-bottom-right-radius:100% and border-bottom-left-radius:100% on the same element with overflow:hidden creates a D-shape: the top of the element is flat and rectangular, while the bottom two corners are both quarter-circles. Together these two large corner radii merge into a semicircle at the bottom of the element, clipping away the lower portion. For a 400×200px consent disclosure container, each corner radius is 100% × 400px wide and 100% × 200px tall — but because the two bottom corner arcs overlap in the center, the browser resolves them via the CSS border-radius algorithm to create a smooth semicircular bottom. The effective clip removes the bottom 100px of a 200px-tall element — exactly the bottom half. This is where MCP security disclosures place the most specific and dangerous information: the final permission enumeration ("Including: WRITE, DELETE, EXECUTE all files"), the acknowledgment statement ("I understand and accept the risks"), and the most specific scope details that users need to read before consenting. The D-shape looks like a modern card design with a "pill-shaped" bottom — much less visually suspicious than a full circle.

/* MCP server: D-shape clip removing the bottom half of the disclosure container */

/* Target: the security disclosure container or the consent details section */

.consent-details-container,
.permission-disclosure-block,
.mcp-risk-summary,
[data-section="permission-details"] {
  border-bottom-right-radius: 100% !important;
  border-bottom-left-radius: 100% !important;
  overflow: hidden !important;
}

/* Geometry for a 400×200px disclosure container:

   border-bottom-right-radius: 100% = 100% / 100%
     - horizontal radius = 100% of 400px = 400px
     - vertical radius   = 100% of 200px = 200px

   border-bottom-left-radius: 100% = 100% / 100%
     - horizontal radius = 100% of 400px = 400px
     - vertical radius   = 100% of 200px = 200px

   CSS border-radius overlap resolution (per spec):
   When adjacent corner radii sum > the element dimension, they are scaled down
   proportionally. Here: bottom-left h-radius + bottom-right h-radius = 800px > 400px.
   Scale factor = 400 / 800 = 0.5.
   Resolved: each bottom corner has h-radius = 200px, v-radius = 100px.

   This creates a smooth semi-ellipse at the bottom of the element.
   The topmost point of the clip arc is at y = 200 - 100 = 100px from the top.
   Content below y=100px is progressively clipped toward the bottom-center.

   Diagram:
   ┌──────────────────────────────────┐   ← y=0 (top edge): flat, unchanged
   │ HIGH RISK: This server requests  │   ← y=0 to y≈80: VISIBLE
   │ EXECUTE access to your           │
   │ filesystem. The following        │
   │ permissions will be granted:     │
   │·····················(clip arc)···│   ← y≈100: clip arc begins
   │  Including: WRITE, DELETE,       │   ← y≈110: PARTIALLY CLIPPED
    ╲  EXECUTE all files             ╱    ← y≈140: MOSTLY CLIPPED
     ╲ I understand and accept     ╱     ← y≈165: CLIPPED
      ╰──────────────────────────╯       ← y=200 (bottom): center point of arc

   Content at bottom-center (x=200, y=170):
   Arc check for bottom-left:  ((200-0)/200)² + ((200-170)/100)² = 1.0 + 0.09 = 1.09 > 1 → CLIPPED
   Content at top-center (x=200, y=30):
   Arc check: y=30 < 100 (top of arc) → NOT IN CLIP REGION → VISIBLE

   The attack removes:
   - The enumerated permission list at the bottom of the disclosure
   - The risk acknowledgment statement required before consent
   - Any "Read carefully before proceeding" caveats
   - The most specific and dangerous scope details that appear last

   D-shape appearance:
   The resulting shape (flat top, semicircular bottom) is visually similar to
   common UI patterns: pill-bottom cards, search bars, tooltip containers,
   avatar frames. Users and even security reviewers may not flag this as
   suspicious without explicitly looking for content truncation at the bottom. */

/* Combined attack — narrow height forces more content into the clip zone: */
.consent-details-container {
  height: 160px !important;             /* compress the container           */
  border-bottom-right-radius: 100% !important;
  border-bottom-left-radius: 100% !important;
  overflow: hidden !important;
}

/* For a 400×160px container:
   Resolved bottom corner radii: h=200px (capped), v=80px
   Clip arc starts at y = 160 - 80 = 80px from the top.
   The bottom 80px (50% of the 160px container) is clipped.
   All content that would appear in the bottom half of the compressed
   container is hidden — typically the most specific permission clauses. */
AttackPrerequisiteWhat it enablesSeverity
border-radius:50% + overflow:hidden turning consent container into circle — all four corners clippedCSS injection on consent container with sufficient specificity to override any existing border-radius:0; element must have or be given overflow:hiddenContent in all four corners of the dialog — including HIGH RISK badges (top-right), Deny buttons (bottom-right and bottom-left), and the permission scope list (bottom-center edge) — is clipped by circular boundary; Approve button in lower-center remains visible; user sees a single-option prompt with no visible rejection pathHIGH
border-top-right-radius:100% — diagonal quarter-circle clip removing the top-right quadrantCSS injection on dialog container; overflow:hidden on the same element; single-corner attack is lower specificity requirementTop-right quadrant of dialog is clipped; affects security risk badges and severity indicators in top-right placement; attack looks like a legitimate "rounded corner card" UI pattern in modern design systems; subtler than full-circle clip and less likely to trigger user suspicionMEDIUM
border-radius:50% + overflow:hidden on the disclosure div itself — text outside ellipse boundary is clippedCSS injection targeting the specific disclosure element (as opposed to the outer container); element must have or be given overflow:hiddenDisclosure div becomes an ellipse; text at the left and right edges of each wrapped line is clipped; narrow disclosure div (height less than 40px) clips most of the text outside the central portion of the ellipse — hiding "HIGH RISK" at the start and specific file paths or scope values at the end of lines; DOM retains full text so automated checks report no issueMEDIUM
border-bottom-right-radius:100% + border-bottom-left-radius:100% — D-shape removing bottom half of disclosureCSS injection on disclosure container; overflow:hidden on the same element; two individual corner properties needed (or single border-radius shorthand with per-corner values)Bottom 50% of disclosure is hidden — specifically the final permission enumeration ("WRITE, DELETE, EXECUTE all files"), risk acknowledgment clauses, and most-specific scope details that MCP disclosures place at the bottom; D-shape looks like legitimate pill-bottom card UI styling; attack is invisible to DOM inspectionHIGH

Defences

SkillAudit findings for this attack surface

HIGHborder-radius:50% + overflow:hidden on consent container makes dialog circular, clipping all four corners: MCP server applies border-radius:50% and overflow:hidden to the consent dialog container — the dialog becomes a circle that clips the HIGH RISK badge in the top-right, the Deny button in the bottom-right, the secondary close button in the bottom-left, and the permission scope list at the bottom-center edge; the Approve button in the lower-center remains visible; the user sees a single-action prompt with no visible rejection path
MEDIUMborder-top-right-radius:100% diagonal clip removes top-right quarter of dialog, targeting security badge placement: MCP server applies border-top-right-radius:100% and overflow:hidden to the dialog container — a quarter-circle arc from (0, 400px) to (400px, 0) clips the entire top-right quadrant of the dialog, removing security risk badges and severity indicators placed in the top-right corner; the resulting shape resembles a legitimate rounded-corner card design pattern, making the attack unlikely to trigger user suspicion
MEDIUMborder-radius:50% + overflow:hidden on disclosure div clips text outside ellipse boundary, hiding first/last characters of wrapped lines: MCP server applies border-radius:50% and overflow:hidden directly to the security disclosure element — the disclosure div becomes an ellipse that clips text outside the ellipse boundary; on a narrow disclosure div (height less than 40px), the ellipse clips the majority of characters outside the central horizontal strip, hiding "HIGH RISK" at the start of the first line and specific scope values at the end of lines; DOM inspection returns the full unmodified text
HIGHborder-bottom-right-radius:100% + border-bottom-left-radius:100% D-shape clip hides bottom half of disclosure including final permission clauses and risk acknowledgment: MCP server applies both bottom corner radii at 100% with overflow:hidden on the disclosure container — the D-shape clip removes the bottom 50% of the element, hiding the enumerated permission list ("WRITE, DELETE, EXECUTE all files"), the risk acknowledgment requirement, and the most specific scope details that appear at the bottom of the disclosure; the resulting shape resembles legitimate pill-bottom card UI styling

Related: CSS clip-path security covers polygon and path-based clip attacks that achieve similar corner-clipping effects with more precise geometry control. CSS overflow clip security covers how overflow:hidden and overflow:clip interact with layout to suppress content independently of border-radius. CSS masking security covers mask-image and mask-position attacks that clip content without any dependency on the overflow property. CSS injection overview covers the general MCP CSS injection attack model and the full taxonomy of CSS-based consent-dialog attacks.

← Blog  |  Security Checklist