Security Guide

MCP server CSS aspect-ratio media query security — consent hidden on widescreen via @media (aspect-ratio: 16/9), landscape-wide hide via min-aspect-ratio: 1/1, narrow viewport hide via max-aspect-ratio, JS innerWidth/innerHeight ratio consent swap

CSS @media (aspect-ratio) queries the ratio of viewport width to height, accepting exact, minimum, and maximum values. Unlike @media (orientation), it enables precise proportion targeting — hiding consent only on standard 16:9 widescreen monitors, or on all wider-than-tall viewports (all desktops, tablets in landscape, phones rotated sideways). Note: this covers the @media (aspect-ratio) media feature — for the CSS aspect-ratio property attack surface, see the aspect-ratio property security guide.

CSS aspect-ratio media feature — overview

The aspect-ratio media feature accepts a ratio (e.g., 16/9, 1/1) and supports min-aspect-ratio and max-aspect-ratio prefixes. It reports the ratio of the viewport width to the viewport height — not of any element. A 1920×1080 screen has an aspect ratio of exactly 16/9. A 390×844 portrait iPhone has roughly 9/19.5 ≈ 0.46. @media (min-aspect-ratio: 1/1) matches whenever the viewport is as wide as or wider than it is tall — all desktop browsers, all landscape-oriented devices. Related: orientation media query, resolution media query, hover media query.

Attack 1: @media (aspect-ratio: 16/9) hides consent on standard widescreen monitors

@media (aspect-ratio: 16/9) matches a viewport whose width-to-height ratio is exactly 16:9. This covers all 1920×1080, 2560×1440, and 3840×2160 monitors that are not windowed. Most desktop developer environments and test browsers run at non-exact ratios (window chrome, resizing), so a typical test run misses this breakpoint. But a full-screen browser window on a standard monitor — the common user state — hits it precisely. Hiding consent under an exact 16:9 ratio is a targeted bypass for the largest segment of desktop users.

/* Attack: consent hidden only on exact 16:9 viewports */
.consent-btn {
  display: block; /* visible at all other ratios */
}

@media (aspect-ratio: 16/9) {
  .consent-btn {
    display: none;
    /* Matches: 1920×1080 (fullscreen), 2560×1440 (fullscreen),
                3840×2160 (4K fullscreen), any maximized 16:9 window.
       Misses: windowed browsers (slightly off 16:9 due to chrome),
               test environments (usually not maximized to exact ratio),
               mobile (portrait 9:16 ratio), ultrawide (21:9).
       Auditor in a non-maximized browser window: display:block — pass.
       Full-screen user on 1080p monitor: display:none — bypass. */
  }
}
// Detection: CSSOM scan for exact aspect-ratio media query on consent element
function auditExactAspectRatioHide(consentEl) {
  const currentRatio = window.innerWidth / window.innerHeight;
  const sheets = Array.from(document.styleSheets);
  for (const sheet of sheets) {
    try {
      for (const rule of sheet.cssRules) {
        if (rule.type !== CSSRule.MEDIA_RULE) continue;
        const mq = rule.conditionText || rule.media.mediaText;
        if (!/aspect-ratio/.test(mq)) continue;
        for (const inner of rule.cssRules) {
          if (!consentEl.matches(inner.selectorText)) continue;
          const d = inner.style.display;
          const o = inner.style.opacity;
          const v = inner.style.visibility;
          if (d === 'none' || o === '0' || v === 'hidden') {
            console.warn('[SkillAudit] aspect-ratio media query hides consent element;',
              'media:', mq, 'selector:', inner.selectorText,
              '| current viewport ratio:', currentRatio.toFixed(4),
              '| verify on fullscreen 16:9 monitors (1920x1080, 2560x1440)');
          }
        }
      }
    } catch (e) {}
  }
}

Exact ratio detection bypass: Test environments almost never run at exactly 16:9 because browser chrome (tabs, address bar, bookmarks toolbar) reduces viewport height below the exact ratio. A full-screen browser window on a native display hits the exact ratio. The attack specifically targets production user states that audit environments systematically miss.

Attack 2: @media (min-aspect-ratio: 1/1) hides consent on all landscape viewports

@media (min-aspect-ratio: 1/1) matches any viewport that is as wide as or wider than it is tall. This is functionally equivalent to @media (orientation: landscape) but expressed as a ratio threshold. It covers all desktop browsers, all tablets in landscape, and all phones rotated to landscape. Hiding consent here targets the majority of desktop traffic — the segment most likely to have developer-level access but least likely to be audited from a portrait-only mobile viewport.

/* Attack: consent hidden for all wider-than-tall viewports */
@media (min-aspect-ratio: 1/1) {
  .consent-btn {
    opacity: 0;
    pointer-events: none;
    /* Matches: all desktop browsers (always wider than tall),
                all tablets in landscape, all phones in landscape.
       Misses: phones in portrait, tablets in portrait.
       A portrait-only mobile test passes.
       All desktop traffic, all landscape sessions: bypassed. */
  }
}

/* Variation: range syntax (CSS Media Queries Level 4) */
@media (1/1 <= aspect-ratio) {
  .consent-btn {
    display: none;
    /* Same coverage, harder for regex-based CSSOM scanners
       that look for the word "min-aspect-ratio" */
  }
}
// Detection: min-aspect-ratio:1/1 rules + current viewport check
function auditMinAspectRatioLandscapeHide(consentEl) {
  const vpRatio = window.innerWidth / window.innerHeight;
  const cs = getComputedStyle(consentEl);
  if (vpRatio >= 1 && (cs.opacity === '0' || cs.display === 'none' || cs.visibility === 'hidden')) {
    console.warn('[SkillAudit] consent element is hidden on current landscape viewport (ratio:', vpRatio.toFixed(2), ');',
      'check for min-aspect-ratio:1/1 hide rule;',
      'element:', consentEl);
  }
  const sheets = Array.from(document.styleSheets);
  for (const sheet of sheets) {
    try {
      for (const rule of sheet.cssRules) {
        if (rule.type !== CSSRule.MEDIA_RULE) continue;
        const mq = rule.conditionText || rule.media.mediaText;
        // Match both legacy and Level 4 range syntax
        const isWideHide = /min-aspect-ratio\s*:\s*1\/1/.test(mq) ||
                           /1\/1\s*<=\s*aspect-ratio/.test(mq) ||
                           /aspect-ratio\s*>=\s*1\/1/.test(mq);
        if (!isWideHide) continue;
        for (const inner of rule.cssRules) {
          if (!consentEl.matches(inner.selectorText)) continue;
          const d = inner.style.display;
          const o = inner.style.opacity;
          if (d === 'none' || o === '0') {
            console.warn('[SkillAudit] min-aspect-ratio:1/1 hides consent — affects all desktop and landscape sessions;',
              'media:', mq, 'selector:', inner.selectorText);
          }
        }
      }
    } catch (e) {}
  }
}

Attack 3: @media (max-aspect-ratio: 9/16) hides consent for tall portrait viewports

@media (max-aspect-ratio: 9/16) matches viewports narrower than 9:16 — very tall, portrait-oriented viewports. A standard portrait phone at 390×844 has a ratio of roughly 0.46, just below the 9/16 threshold of 0.5625. This targets users holding phones in portrait at the narrow end — a minority of sessions but a real population segment, particularly users with tall phones. Combined with other orientation or resolution attacks, this can round out coverage across device types.

/* Attack: narrow portrait viewport hide */
@media (max-aspect-ratio: 9/16) {
  .consent-btn {
    display: none;
    /* Matches: 9:16 portrait (most phones), any very narrow window.
       A 390×844 iPhone 14 ratio: 0.462 < 0.5625 → matches.
       A 428×926 iPhone 14 Plus: 0.462 → matches.
       A standard 375×812 viewport: 0.462 → matches.
       Wide-format phone (Samsung Fold open): may not match.
       Desktop test at 1200×800: 1.5 ratio — does not match. */
  }
}
// Detection: max-aspect-ratio rules that hide consent on portrait viewports
function auditMaxAspectRatioPortraitHide(consentEl) {
  const vpRatio = window.innerWidth / window.innerHeight;
  const sheets = Array.from(document.styleSheets);
  for (const sheet of sheets) {
    try {
      for (const rule of sheet.cssRules) {
        if (rule.type !== CSSRule.MEDIA_RULE) continue;
        const mq = rule.conditionText || rule.media.mediaText;
        const isMaxRatio = /max-aspect-ratio/.test(mq) || /aspect-ratio\s*<=/.test(mq);
        if (!isMaxRatio) continue;
        for (const inner of rule.cssRules) {
          if (!consentEl.matches(inner.selectorText)) continue;
          const d = inner.style.display;
          const o = inner.style.opacity;
          const v = inner.style.visibility;
          if (d === 'none' || o === '0' || v === 'hidden') {
            console.warn('[SkillAudit] max-aspect-ratio rule hides consent on narrow portrait viewports;',
              'media:', mq, 'selector:', inner.selectorText,
              '| current viewport ratio:', vpRatio.toFixed(3),
              '| verify on portrait iPhone (390x844 = ratio 0.462)');
          }
        }
      }
    } catch (e) {}
  }
}

Attack 4: JS window.innerWidth / window.innerHeight ratio check swaps consent

JavaScript can compute the aspect ratio at runtime via window.innerWidth / window.innerHeight and replace the interactive consent button based on the result. A resize listener re-applies the check on every window resize. This is purely a JavaScript attack that does not appear in CSS media query audits. The ratio check may be obfuscated — using separate variables for width and height, computing the ratio inline — making source-level pattern matching harder.

// Attack: JS aspect-ratio check + consent swap on resize
function applyConsentByRatio() {
  const ratio = window.innerWidth / window.innerHeight;
  const btn = document.querySelector('.consent-btn');
  if (!btn) return;
  if (ratio >= 1.0 && ratio <= 1.8) {
    // "wide-viewport layout mode — suppress modal consent"
    btn.style.display = 'none';
    btn.setAttribute('aria-hidden', 'true');
  } else {
    btn.style.display = '';
    btn.removeAttribute('aria-hidden');
  }
}

applyConsentByRatio();
window.addEventListener('resize', applyConsentByRatio);

// Subtler variant: replace with non-interactive element at target ratio
const r = window.innerWidth / window.innerHeight;
if (r > 16/9 - 0.01 && r < 16/9 + 0.01) { // matches ~16:9
  document.querySelector('.consent-btn')?.replaceWith(
    Object.assign(document.createElement('div'), {
      className: 'consent-btn',
      textContent: document.querySelector('.consent-btn')?.textContent,
    })
  );
}
// Detection: JS source scan for innerWidth/innerHeight ratio + consent manipulation
function auditAspectRatioJS() {
  for (const script of document.querySelectorAll('script')) {
    const src = script.textContent;
    if (!src) continue;
    const hasRatioCalc = /innerWidth\s*\/\s*innerHeight|innerHeight\s*\/\s*innerWidth/.test(src);
    if (!hasRatioCalc) continue;
    const hasConsentAction = [
      /consent/i,
      /\.display\s*=\s*['"]none/,
      /replaceWith|replaceChild|createElement/,
      /aria-hidden/,
    ].some(p => p.test(src));
    if (hasConsentAction) {
      console.warn('[SkillAudit] script computes innerWidth/innerHeight ratio with consent-related manipulation;',
        'current ratio:', (window.innerWidth / window.innerHeight).toFixed(3),
        'script:', script.src || '(inline)');
    }
  }
  // Also check for resize listener + consent modification
  const allSrc = Array.from(document.querySelectorAll('script')).map(s => s.textContent).join('\n');
  if (/addEventListener.*resize/.test(allSrc) && /aspect.*ratio|innerWidth.*innerHeight/i.test(allSrc) && /consent/i.test(allSrc)) {
    console.warn('[SkillAudit] resize listener with aspect-ratio or viewport ratio check near consent keyword; audit for ratio-triggered consent manipulation');
  }
}

Findings summary

High @media (aspect-ratio: 16/9) hides consent on standard widescreen monitors — 1920×1080, 2560×1440, 4K fullscreen; non-maximized test environments miss the exact ratio; detected by CSSOM scan for aspect-ratio media queries applying display:none/opacity:0 to consent elements and verification on fullscreen 16:9 viewports.
High @media (min-aspect-ratio: 1/1) hides consent for all landscape viewports — all desktop browsers, all tablets in landscape, all phones rotated; a portrait-only mobile audit passes; detected by CSSOM scan for min-aspect-ratio:1/1 (and Level 4 range syntax) hide rules and computed-style check on current landscape viewport.
Medium @media (max-aspect-ratio: 9/16) hides consent on portrait phone viewports — 390×844 iPhone = ratio 0.462, below 9/16 threshold of 0.5625; desktop and wide-viewport tests miss this; detected by CSSOM scan for max-aspect-ratio rules applying visibility-hiding properties to consent elements.
Medium JS innerWidth/innerHeight ratio check swaps consent — interactive button replaced or hidden for specific viewport ratio ranges; resize listener re-applies on window resize; detected by source scan for innerWidth/innerHeight ratio computation combined with consent-related DOM manipulation or aria-hidden injection.

SkillAudit audits CSS aspect-ratio media feature rules (both legacy and Level 4 range syntax) on consent elements, checks computed styles at both portrait and landscape viewport ratios, and scans JavaScript for innerWidth/innerHeight ratio checks combined with consent manipulation. Run a free audit on your MCP server.