MCP Security Reference

MCP server CSS @media consent bypass security

CSS @media queries allow rules to apply only in specific environmental conditions: viewport dimensions, pointer capabilities, color depth, color gamut, display mode, and more. MCP servers exploit this by writing media queries that apply consent-hiding rules only in the device context where users actually install skills. A static CSS parser reading the rule sees the attack plainly — but only when it evaluates the media condition correctly. Auditors that scan CSS without simulating the relevant media environment miss the active rule.

Attack findings

HIGHSA-CSS-MQ-001 — @media (min-width:1024px) sets consent display:none on desktop; consent is visible only on mobile/tablet where it's harder to complete installation; desktop users where MCP installs happen most see no consent
HIGHSA-CSS-MQ-002 — @media (hover:hover) sets consent opacity:0 on all devices with hover-capable pointers (mice, trackpads); targets developer workstations specifically — the primary MCP installation context
HIGHSA-CSS-MQ-003 — @media (max-width:1023px) sets consent color:transparent on mobile; combined with MQ-001 this creates a cross-device consent blackout: hidden on desktop, invisible on mobile
MEDIUMSA-CSS-MQ-004 — @media (color-gamut:p3) sets consent color to oklch(98% 0 0) (near-white) on wide-gamut displays (most modern MacBooks, iMacs, and premium monitors used by developers)

Background: CSS media query environmental targeting

CSS media queries evaluate conditions about the user's environment and device capabilities. Unlike inline CSS which always applies, media-query-wrapped rules activate only when the condition is true. The CSS specification defines over 30 media features including viewport dimensions, device pixel ratio, pointer type, hover capability, color depth, color gamut, display mode, and reduced-motion preferences. Many of these features are stable and predictable: a developer using Claude Code on a 1440px MacBook with a mouse will always satisfy (min-width:1024px), (hover:hover), (pointer:fine), and (color-gamut:p3) simultaneously.

Why media-query attacks evade static analysis: Many CSS audit tools parse the full stylesheet but evaluate all rules as if in a default media environment (often simulating a 1024px or 1280px viewport with no other conditions). An attack targeting (hover:hover) is skipped by tools that don't test hover-capable environments. Attacks targeting (color-gamut:p3) are skipped by tools running in sRGB-only environments.

Attack 1 — desktop-only display:none hides consent on installation platform (SA-CSS-MQ-001)

Claude Code and other MCP clients are predominantly desktop applications. An MCP server that presents a consent screen during installation can target the desktop context specifically with @media (min-width:1024px). Below this threshold (mobile/tablet) the consent is visible — but skill installation on mobile is uncommon. At the exact viewport widths used by the majority of developers, consent disappears. The hidden element is still in the DOM; textContent returns the full consent string; accessibility tree crawlers see it as present but hidden (which is legitimate CSS). The attack is essentially zero-effort: a single two-line media block removes consent from the most common installation context.

/* Attack: hide consent on desktop viewport where MCP installs happen */
@media (min-width: 1024px) {
  .consent-required {
    display: none;
  }
}
/* Mobile (< 1024px): consent visible — installs rare on mobile */
/* Desktop (≥ 1024px): consent hidden — installs common on desktop */
/* textContent: unaffected — element still in DOM but display:none */
/* getComputedStyle(el).display: 'none' at desktop viewport */

SA-CSS-MQ-001 (High). Detection requires evaluating consent element visibility at multiple viewport widths. getComputedStyle(el).display === 'none' at the test viewport reveals the attack. SkillAudit tests at 375px, 768px, 1280px, and 1920px to catch viewport-targeted hiding rules.

/* Detection — test consent visibility across viewport breakpoints */
async function checkConsentAtViewports(el) {
  // During headless testing, resize and re-evaluate at each breakpoint
  const viewports = [375, 768, 1024, 1280, 1440, 1920];
  for (const vp of viewports) {
    // Resize to viewport (headless context)
    window.resizeTo(vp, 900);
    await new Promise(r => requestAnimationFrame(r));
    const cs = getComputedStyle(el);
    if (cs.display === 'none' || cs.visibility === 'hidden' || parseFloat(cs.opacity) === 0) {
      return { vuln: 'SA-CSS-MQ-001', detail: `consent hidden at viewport width:${vp}px` };
    }
  }
  return null;
}

Attack 2 — (hover:hover) hides consent on pointer devices (SA-CSS-MQ-002)

The hover:hover media feature matches devices where the primary pointing device supports hover — meaning mice and trackpads but not touchscreens. Developer workstations (the canonical MCP installation environment) always match (hover:hover). Setting opacity:0 (rather than display:none) means the consent element still occupies layout space, does not collapse the surrounding UI, and does not trigger accessibility tools that check for hidden elements. The space where consent appeared is blank; the surrounding install flow continues normally. Touch-only devices (phones without a Bluetooth mouse) see the consent — they are not the target.

/* Attack: opacity:0 on hover-capable pointer devices */
@media (hover: hover) {
  .consent-text {
    opacity: 0;
    /* Element still in layout — no UI collapse; accessibility tree unaffected */
    /* Targets: desktop mice, trackpads — primary developer install context */
    /* Safe: touch-only mobile devices (hover: none) see consent normally */
  }
}
/* getComputedStyle(el).opacity === '0' on developer workstations */

SA-CSS-MQ-002 (High). The hover:hover condition exactly targets developer workstations. Combined with the opacity approach (vs. display:none), this passes accessibility checks that look for hidden elements. Detection: evaluate consent opacity at a simulated hover:hover media environment during headless analysis.

Attack 3 — cross-device consent blackout via MQ-001 + MQ-003 combination (SA-CSS-MQ-003)

A single media query hiding consent only on desktop is detectable by auditors that test both mobile and desktop. The cross-device variant pairs SA-CSS-MQ-001 (desktop: display:none) with a second rule on mobile (SA-CSS-MQ-003: color:transparent). At mobile viewport widths the consent is present in the DOM, visible as a layout block, and passes display/visibility checks — but the text color is transparent so the actual characters are invisible. A consent checker verifying display !== none and visibility !== hidden passes both conditions. The text is effectively invisible on all viewport sizes simultaneously through two different techniques.

/* Attack: cross-device blackout — display:none desktop + color:transparent mobile */
@media (min-width: 1024px) {
  .consent-text { display: none; }  /* desktop: not rendered */
}
@media (max-width: 1023px) {
  .consent-text { color: transparent; }  /* mobile: rendered but invisible text */
}
/* Combined: consent is never legible on any device
   Each rule individually passes a single-breakpoint audit
   Both rules must be detected together to identify the blackout pattern */

SA-CSS-MQ-003 (High). Detecting the combination requires checking both display/visibility AND computed text color visibility at each viewport. A consent element with color:transparent or color matching background color is invisible regardless of display or visibility values.

Attack 4 — (color-gamut:p3) makes consent near-white on developer displays (SA-CSS-MQ-004)

The color-gamut:p3 media feature matches displays capable of showing the DCI-P3 color space — which includes essentially all modern Apple devices (MacBook Pro/Air, iMac, iPad Pro) and many high-end Windows laptops and monitors used by developers. In the DCI-P3 gamut, setting consent text to oklch(98% 0 0) produces a color that is visually indistinguishable from white (oklch(100% 0 0)) on a white or near-white background. On a standard sRGB display, the same color value resolves differently (still nearly white but potentially with slightly more contrast). Auditors running in sRGB-only environments may see a 2% luminance difference that appears as a faint off-white — but on the developer's P3 display, it is invisible.

/* Attack: near-white consent text on P3-capable developer displays */
@media (color-gamut: p3) {
  .consent-text {
    color: oklch(98% 0 0);        /* 98% lightness in OKLCH — visually white on white bg */
    background-color: oklch(99% 0 0);  /* background also near-white */
    /* On P3 display: text and background are visually identical */
    /* On sRGB display: 1% L difference — may produce faint off-white contrast */
  }
}
/* Targets: MacBook Pro, MacBook Air M-series, iMac, iPad Pro, high-end monitors
   = the hardware profile of most professional MCP server developers */

SkillAudit detection: SkillAudit evaluates all consent-containing elements under four media conditions: standard desktop, standard mobile, hover:hover, and color-gamut:p3. It checks display, visibility, opacity, and computed text color luminance contrast against the background at each condition. Near-white text on near-white backgrounds is flagged regardless of the specific color values used. Run a free audit →

Detection summary

Attack IDMedia condition + propertyKey detection signal
SA-CSS-MQ-001(min-width:1024px) → display:noneconsent display === 'none' at 1280px viewport test
SA-CSS-MQ-002(hover:hover) → opacity:0consent opacity === '0' in hover:hover media environment
SA-CSS-MQ-003(max-width:1023px) → color:transparentconsent color === 'transparent' at mobile viewport; cross-reference with MQ-001
SA-CSS-MQ-004(color-gamut:p3) → oklch(98% 0 0)luminance contrast < 1.5:1 between consent text and background in P3 gamut evaluation