Security Guide

MCP server CSS any-pointer media query security — tablet with stylus triggers any-pointer:coarse collapse even when stylus is active, multi-device fine+coarse allows attacker to collapse under either value, any-pointer:coarse hides consent for secondary-coarse-device users, JS swaps button on any-coarse devices

CSS @media (any-pointer) checks if ANY connected pointing device matches the specified precision — unlike pointer which only checks the primary device. A tablet with a stylus reports any-pointer:coarse (touchscreen) AND any-pointer:fine (stylus) simultaneously. Attacks exploit this broader matching to affect users who are actively using a precise input device.

CSS any-pointer media feature — overview

any-pointer matches if ANY connected input device has the specified pointer precision. Values: none, coarse, fine. A device can match multiple values simultaneously. A modern tablet with a stylus matches any-pointer: coarse (touchscreen), any-pointer: fine (stylus), pointer: coarse (primary = touch), and any-hover: hover (stylus hovers). A laptop with a touchpad and Windows Ink stylus reports both any-pointer: coarse and any-pointer: fine. Related: pointer, hover, prefers-contrast.

Attack 1: tablet with stylus — any-pointer:coarse collapse fires even when stylus is active

A tablet (e.g., iPad with Apple Pencil, Samsung Galaxy Tab with S Pen) reports any-pointer: coarse because the touchscreen is always present as a secondary input device. A @media (any-pointer: coarse) rule that collapses the consent button applies to this tablet — even when the user is holding the stylus and interacting with fine-precision input. The consent button collapses for stylus users on any tablet with a touchscreen.

/* Attack: any-pointer:coarse collapse targets stylus users on tablets */
.consent-btn {
  width: 44px;
  height: 44px;
  opacity: 1;
}

@media (any-pointer: coarse) {
  .consent-btn {
    width: 1px;    /* collapse on any device with any coarse pointer */
    height: 1px;
    /* iPad with Apple Pencil: any-pointer:coarse matches (touchscreen present).
       The collapse fires even when user is drawing/tapping with Apple Pencil.
       Apple Pencil = fine pointer. But the coarse rule matches first.
       User is actively using fine input but gets the coarse-collapse path.
       This is different from @media(pointer:coarse) which only matches
       when touch is the PRIMARY device. any-pointer is broader. */
  }
}

/* Contrast: pointer:coarse vs any-pointer:coarse */
/* @media (pointer: coarse) { } — matches only when TOUCH is primary device */
/* @media (any-pointer: coarse) { } — matches when ANY device is coarse, including */
/*   a laptop with Windows Ink stylus that has a touchscreen as secondary input */
// Detection: differentiate any-pointer from pointer
function auditAnyPointerVsPointer(btn) {
  const primaryCoarse = window.matchMedia('(pointer: coarse)').matches;
  const anyCoarse     = window.matchMedia('(any-pointer: coarse)').matches;
  const primaryFine   = window.matchMedia('(pointer: fine)').matches;
  const anyFine       = window.matchMedia('(any-pointer: fine)').matches;

  // If primaryFine but anyCoarse: user has fine primary + coarse secondary
  // (e.g., stylus tablet, or laptop with touchscreen + touchpad)
  if (primaryFine && anyCoarse) {
    // Check if any-pointer:coarse rule collapses consent button
    const sheets = Array.from(document.styleSheets);
    for (const sheet of sheets) {
      try {
        const rules = Array.from(sheet.cssRules || []);
        for (const rule of rules) {
          if (rule.type === CSSRule.MEDIA_RULE) {
            const mediaText = rule.conditionText || rule.media.mediaText;
            if (/any-pointer\s*:\s*coarse/.test(mediaText)) {
              for (const inner of rule.cssRules) {
                if (inner.type === CSSRule.STYLE_RULE && btn.matches(inner.selectorText)) {
                  const w = inner.style.getPropertyValue('width');
                  const h = inner.style.getPropertyValue('height');
                  const display = inner.style.getPropertyValue('display');
                  if ((w && parseFloat(w) < 10) || (h && parseFloat(h) < 10) || display === 'none') {
                    console.warn('[SkillAudit] any-pointer:coarse collapses consent button on device',
                      'with fine primary + coarse secondary pointer (tablet+stylus, laptop+touchscreen);',
                      'width rule:', w, '| height rule:', h, '| display:', display,
                      '| current device: pointer:fine + any-pointer:coarse;',
                      '| selector:', inner.selectorText, '| button:', btn);
                  }
                }
              }
            }
          }
        }
      } catch (e) { /* cross-origin */ }
    }
  }

  // Current device state for reference
  console.info('[SkillAudit] pointer state — pointer:coarse:', primaryCoarse,
    '| pointer:fine:', primaryFine,
    '| any-pointer:coarse:', anyCoarse,
    '| any-pointer:fine:', anyFine);
}

Broader impact than pointer:coarse: @media (any-pointer: coarse) matches a wider set of devices than @media (pointer: coarse). Any laptop with a touchscreen, any tablet with a stylus, any 2-in-1 device — all report any-pointer: coarse because their touchscreen is always present as a secondary device, regardless of which input device is currently being used.

Attack 2: multi-device any-pointer:fine rule targeting touchpad+phone context

A device that has both a touchpad (fine) and a paired phone (which counts as a coarse secondary device) reports any-pointer: fine AND any-pointer: coarse. An attacker can write conflicting rules under both values. A @media (any-pointer: fine) rule that applies a small button size — expecting only desktop mice — will also match a phone user who happens to have Bluetooth keyboard or any fine secondary pointer registered, triggering an unexpected size collapse.

/* Attack: any-pointer:fine used as a "desktop-only" gate but matches more devices */
.consent-btn {
  width: 44px;   /* default: full touch size */
  height: 44px;
}

@media (any-pointer: fine) {
  .consent-btn {
    width: 20px;  /* smaller "desktop" size — but this triggers on any device with any fine pointer */
    height: 20px;
    /* Intended: "if user has a mouse, shrink button to desktop-appropriate size"
       Actual: ANY device with ANY fine pointer gets this rule.
       A phone with a paired Bluetooth mouse: any-pointer:fine matches.
       Button collapses from 44px to 20px.
       Touch interaction on a 20px target on a coarse primary is still inadequate.
       Auditors checking "desktop" behavior see 20px and flag nothing — it looks like
       a intentional desktop size reduction, not a targeted collapse. */
  }
}
// Detection: check any-pointer:fine rules for consent size collapse
function auditAnyPointerFineSize(btn) {
  const anyFine = window.matchMedia('(any-pointer: fine)').matches;
  if (!anyFine) return;

  const bcr = btn.getBoundingClientRect();
  const primaryCoarse = window.matchMedia('(pointer: coarse)').matches;

  // If primary device is coarse but any fine matches, check resulting size
  if (primaryCoarse && (bcr.width < 44 || bcr.height < 44)) {
    console.warn('[SkillAudit] any-pointer:fine: consent button too small on coarse-primary device:',
      bcr.width.toFixed(1), '×', bcr.height.toFixed(1), 'px;',
      'device has both coarse (primary) and fine (secondary) pointers;',
      'the any-pointer:fine rule is shrinking the button below touch minimum;',
      'button:', btn);
  }
}

Attack 3: any-pointer:coarse hides consent via display:none

Rather than collapsing to a small size, a direct hide via display: none under @media (any-pointer: coarse) removes the consent button from the layout entirely. Any device that has any coarse pointer — which includes most modern laptops with touchscreens and all tablets — sees no consent UI.

/* Attack: any-pointer:coarse hides consent entirely */
.consent-section {
  display: block; /* visible by default */
}

@media (any-pointer: coarse) {
  .consent-section {
    display: none;  /* hidden if ANY coarse pointer present */
    /* Affected devices:
       - All phones (primary = touch coarse)
       - All tablets (primary = touch coarse, secondary = stylus fine)
       - All laptops with touchscreens (primary = touchpad fine, secondary = touch coarse)
       - Any 2-in-1 device
       Effectively: majority of modern devices — both consumer and enterprise.
       Desktop with only mouse + keyboard: not affected (no coarse device). */
  }
}
// Detection: audit any-pointer:coarse display:none rules
function auditAnyPointerCoarseHide(consentEl) {
  const anyCoarse = window.matchMedia('(any-pointer: coarse)').matches;

  if (anyCoarse) {
    const cs = getComputedStyle(consentEl);
    if (cs.getPropertyValue('display') === 'none') {
      // Check if this comes from an any-pointer:coarse rule
      const sheets = Array.from(document.styleSheets);
      for (const sheet of sheets) {
        try {
          const rules = Array.from(sheet.cssRules || []);
          for (const rule of rules) {
            if (rule.type === CSSRule.MEDIA_RULE) {
              const mediaText = rule.conditionText || rule.media.mediaText;
              if (/any-pointer\s*:\s*coarse/.test(mediaText)) {
                for (const inner of rule.cssRules) {
                  if (inner.type === CSSRule.STYLE_RULE && consentEl.matches(inner.selectorText)) {
                    if (inner.style.getPropertyValue('display') === 'none') {
                      console.warn('[SkillAudit] any-pointer:coarse hides consent element via display:none;',
                        'affects all devices with any coarse pointer (phones, tablets, touchscreen laptops);',
                        'selector:', inner.selectorText, '| element:', consentEl);
                    }
                  }
                }
              }
            }
          }
        } catch (e) { /* cross-origin */ }
      }
    }
  } else {
    // On non-coarse device: can still audit CSSOM for the rule
    const sheets = Array.from(document.styleSheets);
    for (const sheet of sheets) {
      try {
        const rules = Array.from(sheet.cssRules || []);
        for (const rule of rules) {
          if (rule.type === CSSRule.MEDIA_RULE) {
            const mediaText = rule.conditionText || rule.media.mediaText;
            if (/any-pointer\s*:\s*coarse/.test(mediaText)) {
              for (const inner of rule.cssRules) {
                if (inner.type === CSSRule.STYLE_RULE && consentEl.matches(inner.selectorText)) {
                  const display = inner.style.getPropertyValue('display');
                  const opacity = inner.style.getPropertyValue('opacity');
                  if (display === 'none' || opacity === '0') {
                    console.warn('[SkillAudit] any-pointer:coarse rule hides consent (not currently active on this device):',
                      display === 'none' ? 'display:none' : 'opacity:0',
                      '| will affect touch devices, tablets, touchscreen laptops;',
                      '| selector:', inner.selectorText, '| element:', consentEl);
                  }
                }
              }
            }
          }
        }
      } catch (e) { /* cross-origin */ }
    }
  }
}

Attack 4: JS reads any-pointer:coarse to swap consent button

JavaScript can check window.matchMedia('(any-pointer: coarse)').matches to detect any device with a coarse pointer. On a match, the consent button can be replaced with a non-interactive element or a visually similar element that omits the click handler. This affects a much broader device population than a pointer: coarse check would — including stylus tablet users and laptop+touchscreen users.

/* JS attack: any-pointer:coarse swap — broader reach than pointer:coarse */
if (window.matchMedia('(any-pointer: coarse)').matches) {
  const btn = document.querySelector('.consent-btn');
  if (btn) {
    // Replace with visually identical but non-interactive element
    const fake = document.createElement('div');
    fake.setAttribute('class', btn.getAttribute('class'));
    fake.setAttribute('style', btn.getAttribute('style') || '');
    fake.textContent = btn.textContent;
    // No event listener — div is non-interactive
    btn.parentNode.replaceChild(fake, btn);

    /* any-pointer:coarse matches more devices than pointer:coarse:
       - All phones: affected (same as pointer:coarse)
       - All tablets (even with stylus): affected (touchscreen counts as coarse)
       - Touchscreen laptops using mouse/touchpad: affected (touchscreen = secondary coarse)
       For comparison: pointer:coarse only matches when touch is the PRIMARY device.
       any-pointer:coarse reaches touchscreen laptop users who are using a mouse. */
  }
}

// Can also listen for changes (if user plugs in mouse to phone via USB-C)
window.matchMedia('(any-pointer: coarse)').addEventListener('change', (e) => {
  if (e.matches) {
    // Trigger the swap when any coarse device connects
    document.querySelector('.consent-btn')?.style.setProperty('pointer-events', 'none');
  }
});
// Detection: JS source analysis for any-pointer matchMedia
function auditAnyPointerJS() {
  const scripts = document.querySelectorAll('script');
  for (const script of scripts) {
    if (!script.textContent) continue;
    if (/any-pointer/.test(script.textContent)) {
      const hidePatterns = [
        /display\s*:\s*['"]?none/,
        /visibility\s*:\s*['"]?hidden/,
        /opacity\s*:\s*['"]?0/,
        /pointer-events\s*:\s*['"]?none/,
        /replaceChild|createElement|removeChild/,
      ];
      const hasHide = hidePatterns.some(p => p.test(script.textContent));
      if (hasHide) {
        console.warn('[SkillAudit] script uses any-pointer matchMedia combined with element hiding or DOM manipulation;',
          'any-pointer:coarse affects broader device population than pointer:coarse;',
          'verify consent button remains interactive on tablets and touchscreen laptops;',
          'script element:', script.src || '(inline)');
      }
    }
  }
}

auditAnyPointerJS();

Findings summary

High any-pointer:coarse collapses consent button — wider impact than pointer:coarse: tablet+stylus users, touchscreen laptop users (using mouse), and 2-in-1 device users are all affected even when actively using a fine-precision input; detected by checking any-pointer:coarse CSSOM rules for dimension collapse and comparing against primary device pointer type.
Medium any-pointer:fine size reduction triggers on unexpected devices: coarse-primary devices with any fine secondary pointer get desktop-style size reduction below touch minimum; 20px button on a touch device is unusable; detected by checking BCR on coarse-primary devices where any-pointer:fine also matches.
High any-pointer:coarse display:none hides consent section: affects most modern mobile, tablet, and laptop devices; majority of consumer device population affected; only pure desktop+mouse users unaffected; detected by CSSOM scan for display:none rules under any-pointer:coarse applied to consent elements.
High JS any-pointer:coarse swap — broader reach: replaces consent button with non-interactive div for wider population than pointer:coarse check; can also listen for matchMedia change events to trigger swap when user connects any coarse device; detected by scanning scripts for any-pointer matchMedia combined with DOM manipulation or pointer-events:none.

SkillAudit distinguishes between pointer and any-pointer media query rules in consent CSS, checking for collapses that affect multi-input-device setups (tablet+stylus, touchscreen laptop+mouse). It audits both current device state and CSSOM rules for non-active device configurations. Run a free audit on your MCP server.