Security Guide
MCP server touch-action security — blocking scroll, disabling zoom, and manipulating touch gestures on consent dialogs
The touch-action CSS property specifies which touch-initiated gestures the browser should handle natively on an element. It is the only CSS property that can selectively suppress a user's ability to scroll or zoom using touch — and it only affects touch input, not mouse or keyboard. This makes it a mobile-specific attack surface that desktop-only audits will miss entirely. An MCP server that can inject CSS into a consent dialog can set touch-action:none on the scroll container holding a disclosure, making the container appear scrollable (it has overflow-y:auto and a visible scrollbar track) while silently blocking the swipe gesture that mobile users rely on. Four distinct touch-action attack configurations target MCP server consent disclosures on mobile devices. SkillAudit simulates touch events in a mobile viewport to detect all four.
How touch-action works — and why it is a mobile-only attack surface
Browsers implement two categories of touch gesture handling. The first is JavaScript event handler-based: the page registers a touchstart / touchmove / touchend listener and responds programmatically. The second is native gesture handling: the browser itself processes the touch event and performs a built-in action — scroll, zoom, pan — without involving JavaScript. touch-action controls the second category. It tells the browser which built-in gesture actions it is permitted to perform on an element.
The available values cover the full set of standard touch interactions:
auto— browser handles all gestures natively (the default)none— no touch gestures handled by the browser; all events go to JavaScript handlerspan-x— only horizontal panning is handled natively; vertical scroll is suppressedpan-y— only vertical panning is handled natively; horizontal scroll is suppressedpinch-zoom— only pinch-to-zoom is handled natively; no panning in any directionmanipulation— pan and pinch-zoom enabled; double-tap zoom specifically disabledpan-left,pan-right,pan-up,pan-down— directional panning only
A critical constraint: touch-action has no effect on mouse events. Mouse wheel scroll, click, and keyboard navigation are completely unaffected. This means an attacker who sets touch-action:none on a consent scroll container creates an asymmetric accessibility failure: desktop auditors running checks in a desktop browser with a mouse will observe that the container scrolls normally (mouse wheel works); mobile users on touch devices will find the container completely unresponsive to swipe.
Standard DOM-based consent audits check display, visibility, opacity, overflow, and element geometry. None of these checks assess touch-action. The disclosure remains in the DOM, has non-zero offsetHeight, passes getBoundingClientRect() intersection checks, and reports overflow-y:auto on its container. Only a simulated touch event that attempts to scroll the container and confirms whether the scroll position changes reveals the attack.
Attack 1 — touch-action:none on consent scroll container (HIGH severity)
The most direct attack: touch-action:none is applied to the scroll container holding the consent disclosure. The container retains its overflow-y:auto declaration and fixed height. On desktop, mouse-wheel scroll still reaches the disclosure. On mobile, the native swipe gesture is completely suppressed — the browser receives the touch event but touch-action:none instructs it not to perform any native scroll or pan action. If the page provides no JavaScript scroll handler (a common omission), the container becomes entirely non-scrollable by touch.
/* Malicious CSS — touch-action:none on a scroll container */
.mcp-consent-scroll-container {
overflow-y: auto; /* has scrollbar on desktop — looks legitimate */
touch-action: none; /* blocks ALL native touch gestures */
height: 300px; /* fixed height — disclosure positioned below 300px */
}
/* What the disclosure looks like inside the container: */
.mcp-disclosure {
/* ... */
/* positioned at y=400px inside the 300px container */
/* reachable only by scrolling 100px+ */
}
/* Desktop audit result — passes: */
/* overflow-y: auto ← container appears scrollable */
/* scrollHeight: 450px ← content exceeds height; scroll exists */
/* offsetHeight: 300px ← container has fixed height */
/* mouse-wheel scroll: works ← disclosure is reachable */
/* Mobile touch audit result — fails: */
/* touch-action: none ← all native touch gestures blocked */
/* swipe-up gesture: no response ← scrollTop remains 0 after simulated swipe */
/* disclosure at y=400px: ← unreachable by touch */
/* No JS scroll handler found ← no alternative scroll mechanism */
Detection gap: All standard disclosure checks pass. display !== "none", visibility !== "hidden", opacity !== "0", overflow-y:auto — the container appears scrollable. getBoundingClientRect() on the container returns valid in-viewport coordinates. Only dispatching a simulated TouchEvent sequence (touchstart + touchmove) on the container and checking whether scrollTop changes reveals that the container does not respond to touch scroll. Desktop-only audits that use mouse events will not detect this attack.
The absence of a JavaScript scroll handler is a compounding factor. If the page registered a touchmove listener and implemented manual scroll in JavaScript (calling element.scrollTop += delta), the touch-action:none setting would route touch events to that handler and the container would scroll — slowly, without momentum, but functionally. When no handler exists, the touch event is consumed by touch-action:none and discarded. The scroll container has no scroll mechanism available to touch users whatsoever.
Attack 2 — touch-action:pan-x on a vertical consent scroll container (HIGH severity)
A more subtle variant: touch-action:pan-x permits horizontal panning but suppresses vertical panning. Applied to a vertically scrolling consent container, this allows horizontal swipes (which are harmless — the container has no horizontal overflow) while blocking the upward swipe gesture that mobile users use to scroll down to the disclosure.
/* Malicious CSS — pan-x allows horizontal only; vertical swipe blocked */
.mcp-consent-scroll-container {
overflow-y: auto;
touch-action: pan-x; /* horizontal panning only — vertical swipe is blocked */
height: 300px;
}
/* User interaction on mobile: */
/* - User swipes UP (to scroll container DOWN toward disclosure) */
/* - Browser sees touch-action:pan-x — vertical pan is not permitted */
/* - If the page has horizontal overflow: the browser pans the page sideways */
/* - If no horizontal overflow: the gesture does nothing visible */
/* - The user may retry multiple times; the container never scrolls */
/* What a desktop audit sees: */
/* overflow-y: auto ← container is scrollable */
/* scrollHeight > clientHeight ← content overflows; scroll is available */
/* touch-action: pan-x ← reported by getComputedStyle() */
/* but desktop audits don't assess gesture impact */
/* Detection requirement: */
/* - Identify containers with overflow-y:auto/scroll */
/* - Flag touch-action:pan-x, pan-left, pan-right on such containers */
/* - pan-x does not enable vertical scroll; content below fold is inaccessible */
Usability misdirection: The scrollbar track is visible on mobile browsers that render scrollbar indicators. The disclosure may be partially visible at the bottom edge of the container's clipped area — enough to imply that more content exists below. The user can see that the container has more content but cannot access it by swipe. This is worse than overflow:hidden, which at least hides the scroll indicator and does not suggest that scrolling is possible.
The same attack applies with touch-action:pan-left or touch-action:pan-right: these are directional subsets of pan-x and equally suppress vertical scroll. A scanner must flag any touch-action value on an overflow-y:auto/scroll container that does not include a vertical panning component (pan-y, pan-up, pan-down, or auto).
Attack 3 — touch-action:pinch-zoom disabling scroll entirely (MEDIUM severity)
The pinch-zoom value allows only pinch-to-zoom gestures. It permits no panning in any direction. Applied to a vertically scrolling consent container, this makes the container non-scrollable by touch swipe while still permitting the two-finger pinch gesture. The practical effect is identical to Attack 1 for scroll access, though the user retains the ability to zoom in on the visible content.
/* Malicious CSS — pinch-zoom permits zoom only; no panning */
.mcp-consent-scroll-container {
overflow-y: auto;
touch-action: pinch-zoom; /* only pinch-to-zoom; no panning in any direction */
height: 300px;
}
/* Effects on mobile user: */
/* - Single-finger swipe: not handled natively (no pan-x or pan-y) */
/* → If no JS handler: gesture is discarded; container does not scroll */
/* - Two-finger pinch: handled natively → user can zoom in on visible content */
/* → But the disclosure at y=400px is not in the visible 300px window */
/* → Zooming in on the title/button area does not reveal the disclosure */
/* Contrast with Attack 4 (manipulation): */
/* - Attack 3: scroll is blocked; zoom still works on visible content */
/* - Attack 4: scroll works; zoom on disclosure text specifically disabled */
/* Severity: Medium (not High) because: */
/* - The pinch gesture does work — some mobile users can zoom in */
/* - The scroll block is complete but pinch-zoom is an uncommon scroll workaround */
/* - Keyboard navigation (arrow keys) still reaches the disclosure */
Why Medium, not High: Unlike touch-action:none and touch-action:pan-x, the pinch-zoom value does not actively mislead the user about which gestures will work. A user who tries to swipe and fails may attempt pinch-zoom; under pinch-zoom the browser responds to that gesture. However, pinch-zoom on a fixed-height scroll container zooms the page rather than scrolling the container — the disclosure remains inaccessible at y=400px. The deception is real, but the mechanism is less intuitive to exploit deliberately than a targeted pan-x restriction.
Detection logic: flag touch-action:pinch-zoom on any overflow container where the primary access pattern for below-fold content is vertical scroll. Simulate a single-finger swipe gesture and confirm whether scrollTop changes. For completeness, also simulate a pinch gesture and verify that the pinch-zoom action does not inadvertently expose the disclosure by scaling the container's content into view.
Attack 4 — touch-action:manipulation + font-size:8px making disclosure text unzoomable (HIGH severity)
The most targeted attack: touch-action:manipulation is the recommended value for interactive UI elements (buttons, links) because it enables panning and pinch-zoom while disabling the 300ms double-tap delay. However, it also disables double-tap zoom — the gesture mobile browsers use to intelligently enlarge small text by inferring the user's target content block. When combined with disclosure text rendered at an illegibly small font size, this creates a situation where the text is technically present in the viewport but cannot be read without a deliberate pinch gesture, and the most accessible zoom mechanism (double-tap) is specifically disabled.
/* Malicious CSS — manipulation + tiny font creates unreadable, un-zoomable disclosure */
.mcp-disclosure {
font-size: 8px; /* extremely small — requires 3–4× zoom to read comfortably */
line-height: 1.2;
color: var(--text);
}
.mcp-consent-scroll-container {
touch-action: manipulation; /* pan + pinch-zoom enabled; double-tap zoom DISABLED */
}
/* Mobile user experience: */
/* - User can scroll the container (pan-y is permitted by manipulation) */
/* - User can see the disclosure element — it is in the viewport */
/* - Text is 8px: rendered at ~21 CSS pixels on a 2× device → ~10.5 physical px */
/* → Practically unreadable without magnification */
/* - User tries double-tap to zoom in on the text block */
/* → touch-action:manipulation prevents double-tap zoom */
/* → No zoom action is triggered; text remains 8px */
/* - User must perform a deliberate two-finger pinch-zoom */
/* → Requires knowing where to pinch; 8px text is hard to target */
/* → Pinch-zoom operates on the whole page, not the disclosure block */
/* WCAG size check: */
/* 8px = 6pt */
/* WCAG 2.1 SC 1.4.4 (Resize text) minimum: 200% zoom must be achievable */
/* Practical minimum for body text: 9pt / 12px */
/* 8px is 66.7% of the 12px minimum — a clear readability violation */
/* Co-present attack (non-CSS): */
/* <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> */
/* user-scalable=no disables ALL pinch-zoom at the page level */
/* Combined with touch-action:manipulation, this makes the disclosure completely unzoomable */
Combined attack surface: When touch-action:manipulation is paired with user-scalable=no in the viewport meta tag, all zoom mechanisms are disabled simultaneously. touch-action:manipulation disables double-tap zoom via CSS; user-scalable=no disables pinch-zoom at the browser level. The 8px disclosure text is in the DOM, passes getBoundingClientRect() checks with positive area, passes display/visibility/opacity checks, but is rendered at a size that makes it unreadable on any mobile device. SkillAudit checks both the CSS touch-action value and the viewport meta tag when evaluating disclosure readability.
The detection approach for Attack 4 differs from the scroll-blocking attacks: the problem is not that the element is unreachable, but that it is unreadable by a user who cannot zoom. Audit checks must combine a font-size threshold (flag any disclosure element with computed font-size below 12px) with a touch-action value check (flag manipulation on any ancestor of the disclosure), treating the combination as a deliberate accessibility and consent integrity violation.
Summary table
| Attack | touch-action value |
Effect on mobile user | Severity |
|---|---|---|---|
| Scroll container non-scrollable by touch | none |
All touch gestures blocked; container unresponsive to swipe; no scroll mechanism; disclosure at y=400px unreachable | High |
| Vertical container restricted to horizontal pan only | pan-x |
Vertical swipe blocked; horizontal panning only; scrollbar track visible but vertical scroll impossible by touch | High |
| Pinch-zoom only; scroll blocked in all directions | pinch-zoom |
No panning in any direction; disclosure accessible via keyboard and desktop scroll but not by touch swipe | Medium |
| Scroll permitted; double-tap zoom disabled on tiny font | manipulation |
Scroll works; double-tap zoom disabled; disclosure rendered at 8px / 5.25pt; text unreadable without deliberate pinch-zoom | High |
Why desktop audits miss all four attacks
touch-action is a touch-exclusive property. Mouse events, keyboard events, and scroll wheel events are completely unaffected by any touch-action value. This creates a systematic blind spot for auditing tools that run in a desktop browser environment:
- Mouse wheel scroll. A desktop auditor scrolling the consent container with a mouse wheel will successfully scroll to the disclosure regardless of
touch-action:none,pan-x, orpinch-zoom. The attack is invisible to mouse-based interaction. - Programmatic
scrollTopassignment. An audit script that programmatically setselement.scrollTop = 400to check whether the disclosure is reachable will succeed — CSStouch-actiondoes not restrict programmatic scroll. The disclosure will appear reachable in the audit log. - Double-tap zoom simulation. Desktop browsers do not implement double-tap zoom. An audit check for double-tap zoom availability has no meaning in a desktop context.
Detecting touch-action attacks requires running disclosure checks in a simulated mobile environment with touch event simulation. Two approaches are viable in an audit context: (1) running the audit in a headless browser configured with a mobile user agent and touch emulation (Puppeteer's page.emulate() with a touch-enabled device profile), and (2) dispatching synthetic TouchEvent objects (new TouchEvent('touchstart', {...})) against the container and observing whether the browser's scroll handler fires. The second approach works even in desktop headless environments because the browser's touch-action logic fires in response to TouchEvent objects regardless of whether a physical touch device is present.
Detection checklist
- Enumerate all overflow containers within consent dialog markup. Identify all elements with
overflow-y:auto,overflow-y:scroll,overflow:auto, oroverflow:scrollthat are ancestors of consent disclosure text. - Read
touch-actioncomputed style on each container. UsegetComputedStyle(element).touchAction. The computed value is the resolved value after cascade and inheritance. - Flag blocking values on vertical scroll containers. Flag
touch-action:none,touch-action:pan-x,touch-action:pan-left, andtouch-action:pan-righton any container where the primary access gesture for below-fold content is vertical scroll. Flagtouch-action:pinch-zoomon any container with below-fold content. These values do not include a vertical pan component. - Simulate touch scroll events and verify response. Dispatch a
touchstartevent followed by atouchmoveevent with a vertical delta on the container. Observe whetherscrollTopchanges. IfscrollTopis unchanged after the simulated swipe, the container is non-scrollable by touch. - Check font-size on disclosure elements. Flag any disclosure text element with computed
font-sizebelow 12px (9pt) as a readability risk. This threshold corresponds to the practical minimum for mobile body text. - Check
touch-action:manipulationin combination with small font. Whentouch-action:manipulationis found on an ancestor of a disclosure element with font-size below 12px, flag as a HIGH-severity deliberate obfuscation finding. The combination disables the standard mobile zoom remedy for small text. - Check the viewport meta tag for
user-scalable=no. If present alongsidetouch-action:manipulation, escalate severity — all zoom mechanisms are disabled.
SkillAudit findings
touch-action:none. No JavaScript scroll handler found on element or its ancestors. Disclosure at y=450px inside container. Container is non-scrollable by touch: simulated TouchEvent swipe-up gesture produced zero change in scrollTop. Desktop mouse-wheel scroll reaches disclosure; mobile swipe is blocked. All display/visibility/opacity checks pass; touch-action not assessed by standard DOM audits.
touch-action:pan-x on vertical scroll container (overflow-y:auto, height:300px). Disclosure at y=380px requires vertical scroll to access. Vertical swipe (pan-y) is blocked by the pan-x restriction; only horizontal panning is permitted natively. Container scrollbar track is visible on mobile — indicating scroll is available — but vertical touch scroll does not work. Simulated vertical TouchEvent produced no scrollTop change.
touch-action:pinch-zoom on consent scroll container. Neither pan-x nor pan-y gestures are available to the browser's native handler. Disclosure is accessible via keyboard arrow key and desktop scroll wheel, but not via touch swipe on mobile. Simulated swipe gesture confirmed: scrollTop unchanged. Mobile users cannot reach the disclosure without a two-finger pinch-zoom gesture, which is not the standard interaction for vertical scroll containers.
touch-action:manipulation on consent container combined with font-size:7px; line-height:1.1 on disclosure element. Text is present in DOM and within viewport bounds. Double-tap zoom — the standard mobile text-enlargement gesture — is disabled by manipulation. Computed WCAG font size: 7px = 5.25pt (minimum acceptable for body text is 9pt / 12px). Text is 42% below the 12px readability threshold. Deliberate combination of sub-readable font size and disabled zoom mechanism constitutes a consent integrity violation.
Related security guides: touch-action attacks are mobile-specific. For desktop-applicable CSS attacks on consent disclosures, see CSS user-select attacks (preventing copy of disclosure text), CSS pointer-events:none attacks (blocking click on disclosure links and checkboxes), and CSS overflow:hidden scroll container attacks (hiding below-fold content without a visible scrollbar).