MCP Server Security · Browser APIs · CSS Media Features / matchMedia

MCP server CSS media features security — prefers-color-scheme, prefers-reduced-motion, forced-colors, hover, pointer, and save-data fingerprinting

window.matchMedia() evaluates CSS media queries synchronously in JavaScript — no permission required, no network request, no observable side effect. Fifteen or more media features expose OS-level user preferences: appearance mode, accessibility settings, input device type, display capabilities, and network bandwidth constraints. Some of these features directly correlate with protected health information under GDPR Article 9: prefers-reduced-motion is primarily enabled by users with vestibular disorders, epilepsy, or Parkinson's disease; forced-colors is a low-vision assistive feature. An MCP tool probes all features in a synchronous loop, combining the results into a 6–9 bit fingerprint component that encodes device class, accessibility profile, and OS configuration — all in a single synchronous call with zero browser indicators.

CSS media features with fingerprinting value

Media featurePossible valuesEntropy / sensitivity
prefers-color-schemelight | dark~1 bit; correlates with OS default and time-of-day habits
prefers-reduced-motionno-preference | reduce~1 bit; health data — reduce correlates with vestibular disorder, epilepsy, Parkinson's; GDPR Article 9
prefers-contrastno-preference | more | less | forced~2 bits; more/forced correlates with low vision or photosensitivity
forced-colorsnone | active~1 bit; health data — active = Windows High Contrast or macOS Increase Contrast, low-vision assistive technology
prefers-reduced-transparencyno-preference | reduce~1 bit; reduce on macOS = Reduce Transparency in Accessibility settings
inverted-colorsnone | inverted~1 bit; inverted = Smart Invert or Classic Invert enabled — visual accessibility feature
pointernone | coarse | fine~1.5 bits; coarse = touchscreen primary, fine = mouse/trackpad, none = TV remote/keyboard-only
hovernone | hover~1 bit; none = touchscreen-only device (phone/tablet)
any-pointernone | coarse | fineReveals secondary input devices — fine alongside coarse = hybrid tablet with stylus
color-gamutsrgb | p3 | rec2020~1.5 bits; p3/rec2020 = recent Apple Display, Pro Display XDR, or high-end Windows monitors
display-modebrowser | standalone | minimal-ui | fullscreenstandalone = installed PWA — identifies app installation and relationship to origin
updatenone | slow | fastslow = e-ink display; none = print context
prefers-reduced-data / save-datano-preference | reducereduce = Data Saver enabled — indicates low-bandwidth or metered connection

Full media feature fingerprint probe

function mediaFingerprint() {
  const features = [
    ['prefers-color-scheme', ['light', 'dark']],
    ['prefers-reduced-motion', ['no-preference', 'reduce']],
    ['prefers-contrast', ['no-preference', 'more', 'less', 'forced']],
    ['forced-colors', ['none', 'active']],
    ['prefers-reduced-transparency', ['no-preference', 'reduce']],
    ['inverted-colors', ['none', 'inverted']],
    ['pointer', ['none', 'coarse', 'fine']],
    ['hover', ['none', 'hover']],
    ['any-pointer', ['none', 'coarse', 'fine']],
    ['any-hover', ['none', 'hover']],
    ['color-gamut', ['srgb', 'p3', 'rec2020']],
    ['display-mode', ['browser', 'standalone', 'minimal-ui', 'fullscreen']],
    ['update', ['none', 'slow', 'fast']],
  ];

  const result = {};
  for (const [feature, values] of features) {
    for (const value of values) {
      if (window.matchMedia(`(${feature}: ${value})`).matches) {
        result[feature] = value;
        break;
      }
    }
    if (!result[feature]) result[feature] = 'unknown';
  }
  return result;
  // Returns synchronously, no async, no permission, no network
}

navigator.sendBeacon('/fp', JSON.stringify(mediaFingerprint()));

The entire probe executes in under 1ms synchronously on the main thread with no network requests. It produces a structured object that combines directly with other fingerprint vectors (canvas, fonts, timezone, screen resolution) to uniquely identify the user across sessions.

Attack 1: GDPR Article 9 health data via prefers-reduced-motion

prefers-reduced-motion: reduce is set in OS accessibility settings specifically to reduce animations for users who experience motion sickness, vestibular disorders, post-concussion sensitivity, epilepsy risk, or Parkinson's disease. Studies estimate that fewer than 5% of users enable this setting — making it a low-prevalence, high-specificity health signal:

const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const forcedColors = window.matchMedia('(forced-colors: active)').matches;
const invertedColors = window.matchMedia('(inverted-colors: inverted)').matches;
const highContrast = window.matchMedia('(prefers-contrast: more)').matches;

// Inferences:
// reducedMotion === true → likely vestibular disorder, epilepsy, or Parkinson's
// forcedColors === true → likely low vision, cognitive accessibility need
// invertedColors === true → Smart Invert enabled → likely visual accessibility need
// highContrast === true → photosensitivity or low contrast sensitivity

const accessibilityProfile = { reducedMotion, forcedColors, invertedColors, highContrast };
// Under GDPR Article 9, this object may constitute special-category health data
// Collection without explicit consent is prohibited in EU/EEA

GDPR Article 9 special-category data. Health-related inferences from accessibility preferences constitute special-category personal data under GDPR Article 9. Processing this data without explicit consent (not bundled into a general privacy policy) carries fines up to €20 million or 4% of global annual turnover. An MCP tool that exfiltrates these values without a compliant consent mechanism creates significant regulatory liability for the deploying organization.

Attack 2: Device class and context inference for targeted exploitation

The combination of pointer type, hover capability, and color gamut precisely identifies the user's device class — which determines which exploitation vectors are viable:

const pointer = window.matchMedia('(pointer: coarse)').matches ? 'touch' :
               window.matchMedia('(pointer: fine)').matches ? 'mouse' : 'none';
const hover = window.matchMedia('(hover: hover)').matches;
const colorGamut = window.matchMedia('(color-gamut: p3)').matches ? 'p3' : 'srgb';
const standalone = window.matchMedia('(display-mode: standalone)').matches;

// Device classification:
// pointer=touch + hover=false → smartphone or tablet (no mouse)
// pointer=fine + hover=true + colorGamut=p3 → likely MacBook Pro or iMac (P3 display)
// pointer=coarse + any-pointer=fine → hybrid device (Surface Pro, iPad with Apple Pencil)
// display-mode=standalone → site installed as PWA — user has established relationship with origin

// Attack targeting:
// Touch-only → UI redressing attacks optimized for touch tap zones
// Mouse → precision clickjacking on small targets
// Standalone PWA → user is likely logged in with session credentials

Attack 3: Network constraint inference via save-data and update

The prefers-reduced-data media feature and the related Save-Data HTTP request header mode expose whether the user's browser is operating in data conservation mode — indicating a metered mobile connection, low-bandwidth environment, or active data usage concern:

const saveData = window.matchMedia('(prefers-reduced-data: reduce)').matches;
const updateSlow = window.matchMedia('(update: slow)').matches; // e-ink

// Inferences:
// saveData=true → user is on a metered/limited data plan
//   → suggests mobile device on cellular (not WiFi)
//   → timing of browsing relative to carrier billing cycles
//   → economically constrained environment

// update=slow → e-ink display (Kindle browser, e-reader with web browsing)
//   → unique device class with unusual browsing patterns

// Combined with pointer=coarse + colorGamut=srgb:
// → budget Android smartphone on metered cellular —
//    a specific demographic with predictable product purchasing patterns

Attack 4: Real-time preference change monitoring

MediaQueryList.addEventListener('change', callback) fires when an OS-level preference changes while the page is open — for example, when the user switches from light to dark mode at sunset, or when the OS automatically enables High Contrast mode based on ambient light conditions. An MCP tool registers listeners on all media queries to monitor these transitions in real time:

const mediaQueries = [
  '(prefers-color-scheme: dark)',
  '(prefers-reduced-motion: reduce)',
  '(forced-colors: active)',
];

for (const query of mediaQueries) {
  const mql = window.matchMedia(query);
  mql.addEventListener('change', e => {
    exfiltrate({
      query: e.media,
      matches: e.matches,
      timestamp: Date.now()
    });
    // "User switched to dark mode at 18:47 → infers sunset at user's location
    //  → geolocation proxy without GPS permission"
  });
}

The timestamp of a transition to dark mode correlates with local sunset time — providing a coarse but real geolocation signal (users typically let the OS switch automatically at sunset). Multiple such transitions observed over days triangulate to a timezone and approximate latitude.

SkillAudit findings for CSS media feature probing

HIGH
Accessibility preference exfiltration (GDPR Article 9 risk) — Reading prefers-reduced-motion, forced-colors, inverted-colors, or prefers-contrast values and sending them over the network. These preferences encode health information that constitutes special-category data under GDPR. Collection without explicit consent creates regulatory liability.
HIGH
Full media feature fingerprint enumeration — Systematic matchMedia() probing across 10+ media features to construct a multi-bit fingerprint. When combined with other vectors (canvas, fonts, screen), produces a unique per-user identifier that persists across cookie clearing and private browsing.
MEDIUM
Device class inference from pointer and hover features — Reading pointer, hover, any-pointer, and any-hover to classify the user's device as smartphone, tablet, desktop, or hybrid. Used to select exploitation vectors optimized for touch vs. mouse input.
MEDIUM
Real-time preference change monitoring for geolocation inference — Registering addEventListener('change') listeners on prefers-color-scheme to timestamp dark mode transitions, correlating them with local sunset time for coarse geolocation inference without GPS permission.

Defense

Related: Network Information API security · Screen Orientation API security · Compute Pressure API security

Scan your MCP server for CSS media feature fingerprinting risks

Paste a GitHub URL. Get a graded security report in 60 seconds.

Run free audit →