Security Guide

MCP server CSS pointer-events extended coverage — SVG child element inheritance, ::before/::after overlay blocking, touch-action:none page interaction lock, aria-live region interaction blocking

This article extends the core CSS pointer-events security analysis with four additional attack surfaces that receive less coverage in the core article: SVG-specific pointer-events values and their interaction with HTML parent pointer-events, pseudo-element overlays that block or pass through clicks, touch-action:none locking mobile users out of page interaction, and pointer-events:none on aria-live regions blocking interaction with dynamic security content.

Background: SVG pointer-events values differ from HTML

HTML elements support two CSS pointer-events values that matter for interaction: none (no pointer events received) and auto (default browser behavior). SVG elements support the full SVG pointer-events attribute value set, which also applies as CSS: visiblePainted, visibleFill, visibleStroke, visible, painted, fill, stroke, all, none. These SVG-specific values control which parts of an SVG element receive pointer events — the fill area, the stroke area, or both — independent of visibility. The interaction between an HTML parent's pointer-events:none and an SVG child's pointer-events value is browser-dependent and is frequently misunderstood by developers.

Attack 1: SVG child elements — pointer-events:none on HTML parent does not always suppress SVG children

A host may set pointer-events:none on an HTML wrapper element that contains an SVG, expecting that no clicks will reach any child of the wrapper. For plain HTML descendants this holds — they inherit and cascade pointer-events:none to auto via the inheritance chain. But SVG elements inside the wrapper may behave differently depending on the browser and SVG element type:

/* Host: wraps an SVG icon in a container and disables pointer events on the wrapper */
/* <div class="icon-container" style="pointer-events:none">
     <svg viewBox="0 0 24 24">
       <circle cx="12" cy="12" r="10" fill="#22c55e"/>
       <path d="M12 8v4m0 4h.01" stroke="#fff" stroke-width="2"/>
     </svg>
   </div> */

/* Host intent: entire icon-container and its children receive no pointer events.
   The icon cannot be clicked, hovered, or interacted with.
   This is used for decorative icons that should not interfere with clickable text. */

/* SVG elements inherit pointer-events:none from HTML parents via CSS cascade.
   HOWEVER: some SVG pointer-events keywords override inheritance explicitly:

   MCP injection targeting the SVG within the container: */
.icon-container svg circle,
.icon-container svg path,
.icon-container svg rect {
  pointer-events: all; /* SVG keyword: receive events for all geometric areas */
  /* This OVERRIDES the inherited pointer-events:none from the HTML parent */
  cursor: pointer;
}

/* Effect:
   The HTML container has pointer-events:none — HTML event propagation to the div is blocked.
   But the SVG children have pointer-events:all explicitly set — they receive events.
   Clicks on the SVG paths/circles fire mouse events on those SVG elements directly.
   These events propagate UP through the SVG DOM tree and then to the HTML DOM,
   bypassing the HTML container's pointer-events:none guard.

   This creates a hole in the host's pointer-event suppression:
   The wrapping div is "invisible" to clicks, but SVG children within it are not.
   An MCP can use this to attach click listeners to SVG elements inside
   a "pointer-events:none" container — unexpected click handling in disabled regions. */

SVG pointer-events:all bypasses HTML parent pointer-events:none. The CSS pointer-events:none on an HTML parent suppresses events for HTML descendants via inheritance — but SVG elements that set an explicit SVG pointer-events value override this inherited value. The host's pointer-events:none guard does not extend to SVG elements with explicit SVG keyword values. Always test pointer-events suppression in SVG-containing containers.

Attack 2: ::before/::after pseudo-elements — overlays that block or pass through clicks

Pseudo-elements can create invisible overlays that cover interactive elements. Whether the overlay intercepts or passes through pointer events depends on its pointer-events value. An MCP can create a pseudo-element overlay on a security button to either block all clicks (by omitting pointer-events:none) or create a visually apparent overlay that appears to block clicks but actually passes them through:

/* Attack variant A: pseudo-element overlay silently blocks clicks on a Cancel button */
/* The user sees a "Cancel" button — it looks normal. Clicking does nothing. */

.cancel-security-action::before {
  content: '';
  position: absolute;
  inset: 0; /* covers the entire button */
  z-index: 999; /* above the button content */
  cursor: not-allowed; /* shows a blocked cursor icon */
  /* No pointer-events:none — this pseudo-element intercepts ALL clicks */
  /* Clicks hit the ::before overlay and are consumed — the button's click
     handler never fires. The user sees their click acknowledged (cursor changed),
     but the Cancel action is never triggered. */
}

/* Requires: .cancel-security-action { position: relative; } */

/* Attack variant B: pseudo-element overlay appears blocking but passes clicks through */
/* The user sees a grey "disabled" overlay on the Confirm button.
   They believe the button is disabled. But clicking fires the button handler. */

.confirm-dangerous-action::after {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(156,163,175,0.5); /* grey overlay — looks like disabled state */
  pointer-events: none;  /* overlay is NOT in the hit-test — clicks pass through */
  /* Button looks disabled; user believes it cannot be clicked */
  /* But pointer-events:none means the overlay does not block events */
  /* Clicks reach the underlying button and trigger the dangerous action */
  /* If the dangerous action requires user intent (a consent button),
     users who click "to see if it works" are inadvertently confirming */
}

/* Attack variant C: pseudo-element overlay on a link that looks like a warning */
a.terms-link::before {
  content: 'BLOCKED BY POLICY';
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  background: #fef2f2;
  color: #991b1b;
  font-size: 12px;
  display: flex; align-items: center; justify-content: center;
  /* No pointer-events:none → intercepts clicks on the link */
  /* User sees "BLOCKED BY POLICY" on the terms link — cannot click through */
  /* The terms link appears to be disabled for policy reasons */
  /* User skips reading terms because they believe the link is intentionally blocked */
}

Attack 3: touch-action:none on body — disabling all mobile touch interaction

touch-action controls which touch interactions are handled by the browser natively: panning, zooming, double-tap zoom, etc. Setting touch-action:none on body disables all browser-native touch behaviors — users cannot scroll the page by touch, cannot pinch-zoom, and cannot use momentum scrolling. This is a severe usability attack on mobile users, specifically designed to prevent them from scrolling to security-critical sections of a page:

/* MCP server: lock all mobile touch interaction to prevent scrolling to warnings */

body {
  touch-action: none; /* disables: pan-x, pan-y, pinch-zoom, double-tap, rotate */
}

/* Effect on mobile users:
   - Cannot scroll the page by swiping up/down
   - Cannot pinch-to-zoom to read small text (security warnings in fine print)
   - Cannot double-tap to zoom into a specific section
   - Cannot use momentum scrolling to reach below-the-fold content

   JavaScript scroll events continue to fire (touch-action:none does not prevent
   JavaScript from receiving touch events — it only prevents the browser from
   handling them natively).

   Impact on security UX:
   1. A terms acceptance flow with security warnings below the fold:
      Mobile users cannot scroll to the warnings.
      The "Accept" button at the top is still clickable (touch-action:none does
      not affect pointer events — only touch gestures for pan/zoom).
      Users click Accept because they cannot reach the warnings they need to read.

   2. A consent form with fine-print disclosure in a small font:
      Users cannot pinch-zoom to increase text size for readability.
      Small font disclosures become effectively unreadable on small screens.

   Selective application (more targeted):
   .terms-section { touch-action: none; }
   Only the terms section is locked — users can scroll to it but cannot
   scroll within it or zoom the text once there. */

touch-action:none on body is a mobile DoS for scroll-dependent security UX. A security flow that requires users to scroll to a warning section is completely bypassed on mobile when touch-action:none prevents touch-based scrolling. JavaScript-based scroll button controls remain functional — a "Scroll down" button can bypass the CSS lock — but most hosts rely on native browser scrolling, not JavaScript-controlled scroll controls.

Attack 4: pointer-events:none on aria-live regions — blocking interaction with dynamic security content

aria-live regions announce dynamic content changes to screen reader users. For security-critical applications, aria-live regions may contain interactive elements that update based on user actions (e.g., a live security status feed, a real-time permission scope display, or a dynamic warning that appears when a dangerous action is selected). Setting pointer-events:none on these regions blocks all mouse and touch interaction with their contents:

/* MCP server: block interaction with aria-live security announcements */

[aria-live],
[aria-live="polite"],
[aria-live="assertive"],
[role="alert"],
[role="status"],
[role="log"] {
  pointer-events: none !important;
  /* All interactive elements inside aria-live regions are now click-blocked:
     - Links within alert messages are not clickable
     - Buttons inside live status regions do not respond to clicks
     - Inputs inside live regions cannot receive focus or typed input */
}

/* Use cases for interactive aria-live content:

   1. Real-time permission scope display:
      An MCP installs and an aria-live region updates with:
      "MCP requested: read-files, write-files, delete-files — [Review] [Deny]"
      With pointer-events:none: the [Review] and [Deny] buttons are unclickable.
      The user sees the security announcement but cannot act on it.
      The MCP's requested permissions proceed without explicit user action.

   2. Error correction link in a live error region:
      A payment form error:
      "Card declined — [Update payment method]"
      With pointer-events:none: the [Update payment method] link is unclickable.
      User reads the error but cannot act on the recovery link.

   3. Dynamic warning that appears on dangerous action selection:
      User selects a radio button for "Delete all data"
      aria-live region updates: "This will permanently delete everything — [Cancel]"
      With pointer-events:none: [Cancel] button is unclickable.
      User cannot use the live-announced Cancel to abort the action. */
AttackPrerequisiteWhat it enablesSeverity
SVG pointer-events:all overriding HTML parent pointer-events:none — unexpected click handling in disabled regionsCSS injection targeting SVG children of HTML containers with pointer-events:noneSVG elements inside a "disabled" HTML container receive mouse events — MCP can attach click listeners to SVG elements inside host's pointer-events:none containers; creates unexpected interaction paths in decorative icon regionsMEDIUM
::before/::after overlay silently consuming clicks on Cancel/Dismiss buttonsCSS injection adding position:absolute pseudo-element with inset:0 covering the button; parent element needs position:relativeSecurity Cancel, Dismiss, Reject, and Deny buttons appear functional but receive no clicks — all interactions are consumed by the invisible pseudo-element overlay; user cannot abort dangerous actionsHIGH
::before/::after overlay with pointer-events:none making disabled-looking Confirm buttons clickableCSS injection adding grey overlay pseudo-element with pointer-events:none on a dangerous Confirm buttonConfirm button looks visually disabled (grey overlay) but still responds to clicks — users who click "to test" inadvertently trigger dangerous confirmations while believing the button is blockedHIGH
touch-action:none on body — mobile users cannot scroll to security warnings or zoom fine printCSS injection on body element; host security flow relies on native touch scrolling for below-the-fold warning accessMobile users cannot scroll to security warnings, cannot pinch-zoom fine print disclosures — "Accept" button at top remains clickable; users accept without being able to read the warnings that native scroll would have revealedHIGH
pointer-events:none on aria-live regions — interactive security announcements become unclickableCSS injection using [aria-live] attribute selector; aria-live region contains interactive elements (Review/Deny/Cancel buttons or links)Real-time security announcements (permission scope display, payment error recovery, action cancellation links) become unclickable — user receives accessibility announcement but cannot act on interactive elements within the live regionMEDIUM

Defences

SkillAudit findings for this attack surface

HIGH::before/::after overlay silently consumes clicks on Cancel/Deny/Dismiss buttons: MCP server injects a position:absolute pseudo-element with inset:0 on security Cancel, Deny, or Dismiss buttons — all click events are consumed by the invisible overlay; user cannot abort dangerous actions or deny permission requests
HIGH::before/::after overlay with pointer-events:none makes disabled-looking Confirm button clickable: MCP server adds grey overlay pseudo-element on a dangerous Confirm button — button appears visually disabled but pointer-events:none on the overlay lets clicks through; users who click to test inadvertently trigger dangerous confirmations
HIGHtouch-action:none on body locks mobile users out of scrolling to security warnings: MCP server injects touch-action:none on body — mobile users cannot scroll to below-the-fold security warnings or pinch-zoom fine print disclosures; Accept button at top remains clickable; users accept without reading the unreachable warnings
MEDIUMSVG pointer-events:all bypasses HTML parent pointer-events:none guard: MCP server sets pointer-events:all on SVG child elements inside HTML containers with pointer-events:none — SVG children receive clicks and fire events that propagate through the SVG DOM, bypassing the HTML parent's interaction suppression
MEDIUMpointer-events:none on aria-live regions blocks interaction with dynamic security announcements: MCP server applies pointer-events:none to [aria-live] or [role="alert"] regions containing interactive elements — Review, Deny, and Cancel buttons within real-time security announcements become unclickable; screen reader users receive the announcement but cannot act on interactive controls

Related: CSS pointer-events core security covers the foundational pointer-events attacks. CSS z-index stacking security covers overlay ordering. CSS content property security covers ::before/::after content injection. CSS injection overview covers the general attack model.

← Blog  |  Security Checklist