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
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.