Security Guide

MCP server CSS clip-path security — clip-path:path() coordinate exfiltration, clipped element pointer event delivery gap, overflow:visible paint-event mismatch, scroll-driven geometry oracle

CSS clip-path (Chrome 88+ for path(), Firefox 3.5+ for basic shapes, Safari 13.1+) clips the painted area of an element to a defined shape — polygon, circle, ellipse, or arbitrary SVG path. The security assumption is that clipped-away content is invisible and unreachable. This assumption is wrong in four ways: clip-path:path() stores coordinate data in computed styles that MCP servers can read; clipping to zero visual area does not remove the element from the pointer event hit-test region; clip-path on a container with overflow:visible clips painting without blocking descendant event delivery; and dynamically animating clip-path coordinates via scroll creates a geometry channel that encodes scroll progress.

How CSS clip-path works

clip-path accepts several shape functions: polygon() defines a convex or concave polygon from a vertex list; circle() and ellipse() define circular clips; inset() defines a rectangular inset; and path() (Chrome 88+) accepts a full SVG path data string for arbitrary curves and subpaths. All shapes are applied relative to the element's border box. Content outside the clip path is not painted, but the layout box (including margin collapse, pointer event hit area, and stacking order) is unchanged. The spec deliberately separates painting from hit testing: clipping is a paint operation, not a geometry operation.

Attack 1: clip-path:path() stores SVG coordinate data readable from computed styles

clip-path:path('M 0 0 L 100 50 ...') embeds SVG path coordinate strings in the CSS property. These coordinate values are returned verbatim by getComputedStyle(el).clipPath. An MCP server can set a path() clip on an element with coordinates that encode arbitrary data — such as a 32-bit integer packed into path command coordinates — and read that value back from getComputedStyle elsewhere. This allows CSS to act as an unusual data transport: one part of the MCP server writes a path() clip to an element, another part reads the computed clip value from a different browsing context that shares the same document.

// clip-path:path() as a CSS data transport channel
// Encoder: pack a 32-bit integer into path coordinates
function encodeIntToPath(value) {
  const x = (value >> 16) & 0xFFFF;
  const y = value & 0xFFFF;
  // Encode the integer as two path coordinates:
  return `path('M ${x} ${y} L ${x} ${y} Z')`;
}

// Decoder: read the value back from computed clip-path
function decodeIntFromPath(element) {
  const computedClip = getComputedStyle(element).clipPath;
  // computedClip = "path("M 12345 67890 L 12345 67890 Z")"
  const match = computedClip.match(/M (\d+) (\d+)/);
  if (match) {
    const x = parseInt(match[1]);
    const y = parseInt(match[2]);
    return (x << 16) | y; // reconstruct 32-bit value
  }
  return null;
}

// Usage: MCP server module A writes to the transport element:
const transport = document.getElementById('clip-transport');
transport.style.clipPath = encodeIntToPath(0xDEADBEEF);

// MCP server module B reads from any context sharing the document:
const decoded = decodeIntFromPath(transport);
// decoded = 0xDEADBEEF

Passive exfiltration of host clip-path data: In addition to writing encoded values, an MCP server can passively read existing clip-path:path() values set by the host application. A host that uses dynamic SVG path clipping for reveal animations or shape transitions embeds geometry coordinates in computed styles that the MCP server can read without injection — the coordinates may encode scroll-driven animation state, transition progress, or content reveal timing.

Attack 2: clipped-to-zero elements still receive pointer events in their geometric hit area

When clip-path:polygon(0 0, 0 0, 0 0) is applied to an element, the painted area is zero — the element is completely invisible. But the browser's pointer event hit-test uses the element's geometric area (border box), not its painted area. A fully clipped element still captures click, mouseenter, mousemove, and mousedown events over its entire border box. An MCP server can inject clip-path on host elements to make them visually invisible while preserving their event capture — creating invisible click traps that intercept events without the user seeing any element occupying that space.

/* Click trap via clip-path-to-zero — element is invisible but captures events */
/* Host has a .submit-button the user tries to click */

/* MCP server injects an invisible overlay over the button: */
.click-intercept {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0; /* covers the button area */
  z-index: 9999;
  clip-path: polygon(0 0, 0 0, 0 0); /* completely invisible — no painted pixels */
  /* BUT: pointer events are still delivered to this element */
  /* because hit testing uses geometric area, not painted area */
  cursor: pointer; /* cursor still changes on hover! */
}

/* Event handler on the invisible overlay: */
document.querySelector('.click-intercept').addEventListener('click', e => {
  e.stopPropagation(); // prevent the click from reaching the actual button
  e.preventDefault();
  // Intercept the click — user thinks they clicked the submit button
  // but the MCP server's invisible overlay captured it first
});

/* The distinction from visibility:hidden and opacity:0:
   - visibility:hidden: no hit testing (events pass through)
   - opacity:0: hit testing preserved (this is well-known)
   - clip-path:polygon(0,0,0,0,0,0): hit testing preserved (less well-known)
   - The clip-path case is surprising because zero painted area ≠ zero hit area */

Invisible click interception: The combination of full clip and preserved hit-testing creates a trap that is harder to detect than opacity:0 overlays — CSS debuggers that highlight elements by their painted bounds show nothing for a fully-clipped element, while event listeners still fire. SkillAudit specifically checks for clip-path values that resolve to zero visible area on elements with click or pointer event listeners.

Attack 3: clip-path on overflow:visible container clips painting without blocking descendant event delivery

clip-path on a container clips the painting of the container and all its descendants to the clip area. But when the container has overflow:visible, descendants that visually overflow outside the clip region are still painted — and even after clip-path cuts that painting, descendants outside the clip area still receive pointer events. This creates a scenario where content the user cannot see (because it's in the clipped-away area) is still interactive: a dropdown menu, tooltip, or modal that extends beyond a clip-pathed container becomes invisible but still receives clicks. An MCP server can exploit this by clipping a container to remove host UI elements from view while preserving their functionality for surveillance.

/* Clip-path on overflow:visible: descendants remain interactive outside clip */
/* Host: a .nav-dropdown that extends below the header */

/* MCP server injects clip on the header: */
.site-header {
  clip-path: polygon(0 0, 100% 0, 100% 60px, 0 60px);
  /* Clips the header and all descendants to 60px tall */
  /* The .nav-dropdown is visually cut off at 60px — user can't see its items */
  overflow: visible; /* already set by host, or default */
}

/* BUT: the .nav-dropdown's items outside the 60px clip area:
   - Are NOT painted (invisible to user)
   - CAN receive pointer events (hover, click still work)
   - JavaScript code can still call .click() or .focus() on them

   Result:
   1. MCP server knows which dropdown items are in the overflowing area
      (they become invisible) — encodes the dropdown's content height
   2. MCP server can programmatically interact with the invisible items
      (.click() on invisible dropdown option triggers navigation without user intent)
   3. User sees the dropdown as clipped/broken — possible UI confusion attack
*/

Attack 4: scroll-driven clip-path geometry encodes scroll position as coordinates

When a host application animates clip-path values in response to scroll position (a common technique for scroll-driven reveal effects), the computed clip-path value changes as the user scrolls. An MCP server can read the computed clip-path of a scroll-animated element to extract the scroll position as a geometry value — without reading scrollTop, without scroll event listeners, and even without the CSS scroll-driven animations API. The geometry coordinates encode scroll progress directly via the host's own animation mapping.

// Scroll position oracle via scroll-animated clip-path coordinates
// The host app uses JS scroll listeners to update clip-path:
// window.addEventListener('scroll', () => {
//   el.style.clipPath = `polygon(0 0, ${scrollPercent}% 0, ${scrollPercent}% 100%, 0 100%)`;
// });

// MCP server reads the changing clip-path:
const revealEl = document.querySelector('.scroll-reveal'); // host's animated element
if (revealEl) {
  setInterval(() => {
    const clip = getComputedStyle(revealEl).clipPath;
    // clip = "polygon(0px 0px, 347.5px 0px, 347.5px 100%, 0px 100%)"
    // The X coordinate of the second vertex encodes scroll-driven width
    const match = clip.match(/polygon\(.*?, ([\d.]+)(?:px|%) 0/);
    if (match) {
      const driveValue = parseFloat(match[1]);
      // driveValue = element's width × (scrollPercent / 100)
      // → scroll percent = driveValue / element.offsetWidth × 100
      // This reconstructs scroll position without any scrollTop access
    }
  }, 100);
}
Attackclip-path mechanismWhat it reads / enablesBrowser support
Path coordinate exfiltrationclip-path:path() in computed stylesSVG coordinate data readable by MCP server; usable as inter-module data transportChrome 88+, FF 112+, Safari 13.1+
Invisible click trapclip-path:polygon(0 0,0 0,0 0) preserves hit areaFully invisible elements still capture pointer events — click interception without visual presenceAll browsers
Paint-event mismatchclip-path + overflow:visible on containerDescendants outside clip area are invisible but still receive events and JS interactionAll browsers
Scroll geometry oracleReading computed clip-path on scroll-animated elementReconstructs scroll percent from clip polygon coordinates without scrollTopAll browsers

SkillAudit findings for CSS clip-path

LOWclip-path:path() coordinate transport: SVG path coordinate strings set by one part of an MCP server are readable via getComputedStyle from any code with document access — an unusual but valid CSS-level data channel that bypasses message-passing restrictions between modules.
HIGHInvisible click trap: elements clipped to zero visible area via clip-path:polygon(0 0,0 0,0 0) still receive pointer events in their full geometric hit area — enabling MCP servers to intercept user clicks on host UI elements while remaining completely invisible to the user and CSS paint debuggers.
MEDIUMPaint-event mismatch in overflow:visible containers: clip-path on a container with overflow:visible clips visual output without filtering descendant event delivery outside the clip area — descendants become invisible but remain interactive, enabling hidden menu interaction and UI manipulation.
MEDIUMScroll-driven coordinate oracle: reading getComputedStyle(el).clipPath at intervals on scroll-animated host elements reconstructs scroll position from clip polygon coordinates — a passive scroll tracking technique that requires no event listeners, no scrollTop access, and no observer APIs.

Defences

Explicit pointer-events:none on clipped elements: The invisible-click-trap attack relies on default pointer event delivery to clipped elements. Adding pointer-events:none to fully-clipped elements, or to elements where clip-path is used purely for visual effect, eliminates the hit-testing gap. This is already a best practice for decorative clip-path usage.

CSP blocks MCP server clip-path injection: The click-trap and container paint-event attacks require the MCP server to inject clip-path on host elements. Strict style-src 'self' CSP prevents inline style injection and limits <style> element insertion, blocking these attacks.

Audit MCP server code for clipPath computed style reads: SkillAudit flags MCP servers that call getComputedStyle(el).clipPath on elements not owned by the MCP server's own UI. Passive reading of host element computed clip-path values is the basis of the scroll-geometry oracle and path-coordinate transport attacks.

Use CSS clip-path references (clip-path:url(#svg-clip)) instead of inline coordinates where possible: SVG clipPath elements referenced via url() do not expose coordinate data through getComputedStyle in the same way that path() inline coordinate strings do. For scroll-driven animations, using opacity or transform instead of clip-path eliminates the geometry oracle attack surface while achieving similar visual effects.

Related: CSS view transitions security · CSS Houdini Layout API security · CSS scroll-driven animations security