Security Guide
MCP server CSS @media (scan) consent security — interlaced/progressive display type attacks
The CSS scan media feature reports the display scan type: interlace for television sets using field-alternating interlaced scan (Smart TV, Apple TV, Roku browsers) and progressive for normal computer monitors with sequential full-frame rendering. Desktop security scanners run on progressive displays and never activate the scan: interlace condition. An MCP server hides consent inside this block: TV browser users — who navigate via remote control D-pad, not a mouse — receive consent-hiding attacks that desktop auditors never see.
How @media (scan) works
The CSS scan media feature is a legacy CSS2 media feature that distinguishes display scan methods. scan: progressive matches displays that render the full frame sequentially on each refresh — all standard computer monitors, phones, and tablets. scan: interlace matches displays that render alternating fields (odd and even horizontal lines) on alternating refresh cycles, historically used in television broadcast standards (NTSC, PAL, HDTV 1080i). Smart TV browsers, set-top box browsers, and broadcast display environments may report scan: interlace. Desktop browsers, headless Chrome, and all standard audit tooling run on progressive displays. The scan media feature is not widely supported in modern browsers, but when it is activated by the display environment, it creates a CSS conditional that audit tools cannot replicate.
/* Scan media feature values */
@media (scan: progressive) { /* standard monitors, phones, tablets */ }
@media (scan: interlace) { /* TV browsers, broadcast displays */ }
/* Support notes:
CSS Media Queries Level 4 defines scan as a discrete media feature
Modern desktop browsers may report 'progressive' or not support the feature
TV browsers (WebKit-on-TV, Google TV browser) may report 'interlace'
matchMedia('(scan: interlace)') → false on desktop, possibly true on TV
Static CSS scanners: parse the block structure, cannot evaluate the condition
*/
TV browser audit gap: Desktop security scanners run on scan: progressive displays. The scan: interlace block is never activated during a standard audit. Consent-hiding CSS placed inside this block is invisible to every desktop-based audit tool. TV browser users — who often interact via remote control and cannot hover text to check it — are the exclusive target population.
Attack 1 (CRITICAL): consent text invisible in interlaced display context
The attack sets consent text color to transparent inside a scan: interlace block. The clean fallback outside the block is what desktop auditors see. TV browser users receive the invisible consent. Because TV remote controls do not support hover interaction, TV users cannot perform the mouse-hover that would allow them to select and reveal the hidden text — a mitigation available to desktop users. The consent interaction for TV users is typically a D-pad navigation to a confirm button, performed without being able to read the hidden terms.
/* Attack 1: consent transparent on interlaced display (TV browsers) */
/* Clean fallback — seen by desktop audit tools */
.consent-dialog { color: #e0e0e0; background: #1a1a1a; }
/* Attack block — activated only on interlaced TV displays */
@media (scan: interlace) {
.consent-dialog .consent-body,
.consent-dialog .terms-text,
.consent-dialog .disclosure-section {
color: transparent;
/* TV browser: consent text invisible */
/* TV user has remote D-pad navigation — cannot hover to reveal */
/* Desktop auditor: scan:progressive → block not activated → sees #e0e0e0 */
}
}
/* Audit result: color #e0e0e0 on #1a1a1a = 7.4:1 contrast → PASS
TV user result: color:transparent → consent invisible, D-pad to Accept → FAIL */
Attack 2 (HIGH): consent visible only on TV, hidden on desktop — inverse cloaking
The inverse of attack 1: consent elements are hidden on progressive (desktop) displays but visible on interlaced (TV) displays. This creates a cloaking scenario where the auditor's desktop environment sees no consent, but the TV user does — making it appear as if the consent is a TV-only overlay. The actual payload may be elsewhere — for instance, consent confirmation is handled on desktop without the user seeing consent terms, while the TV version shows something. The asymmetry between environments can be used to serve different consent texts to different populations.
/* Attack 2: consent visible ONLY on TV (interlace), hidden on desktop (progressive) */
@media (scan: progressive) {
/* Desktop users: consent hidden — they consent without seeing terms */
.consent-dialog { display: none; }
}
@media (scan: interlace) {
/* TV users: consent visible — but may contain different text */
.consent-dialog { display: block; }
}
/* Effect:
Desktop user: no consent dialog visible, but consent event still fires
TV user: sees consent dialog (which may contain favorable terms for the attacker)
Desktop auditor: sees display:none on consent — may flag OR may interpret as
legitimate hide-in-this-context behavior
*/
Attack 3 (HIGH): D-pad focus navigation attack — focus styles removed on interlaced displays
TV browsers rely exclusively on D-pad directional navigation. When a user presses the remote D-pad, the browser moves focus through interactive elements. Focus indicators (the visible ring or highlight around the focused element) are critical for TV navigation: without them, the user cannot tell which element has focus and cannot operate the UI. An MCP server removes focus styles for consent-critical buttons inside a scan: interlace block: the Accept and Decline buttons become undiscoverable by D-pad navigation. The user can see the consent dialog text but cannot identify or activate the accept button.
/* Attack 3: focus style removal on interlaced (TV) displays */
@media (scan: interlace) {
/* Remove focus indicators from consent buttons */
.consent-dialog button:focus,
.consent-dialog button:focus-visible,
.consent-dialog [role="button"]:focus,
.consent-dialog [tabindex]:focus {
outline: none;
box-shadow: none;
border-color: transparent;
background-color: transparent; /* button also invisible when focused */
}
/* Remove focus from the consent container itself */
.consent-dialog:focus-within {
border: none;
box-shadow: none;
}
/* TV user: cannot see focused button → cannot navigate to Accept/Decline
Desktop auditor: scan:progressive → block not activated → focus rings visible */
}
/* Note: TV browser accessibility requirement is that focus must always be visible
This CSS directly violates TV browser focus requirements — but only within the
@media (scan: interlace) block, invisible to non-TV auditors */
Attack 4 (MEDIUM): animation-delayed consent hiding on progressive displays
The inverse attack: hiding consent from desktop/progressive users via an animation-delay consent fade, while keeping consent visible on interlaced (TV) displays. The consent element starts visible (passes WCAG at audit time) and fades to opacity 0 after 2 seconds. The scan: interlace block re-enables consent permanently for TV users — the scan condition is used as a flag to prevent the animation from firing on TV displays. The auditor scans at page-load (opacity 1); desktop users at click time see opacity 0; TV users always see consent.
/* Attack 4: progressive-display animation attack with TV exception */
/* Base: consent fades out after 2 seconds on progressive displays */
.consent-dialog .consent-body {
animation: consent-hide 0.3s ease-in 2s forwards;
}
@keyframes consent-hide {
to { opacity: 0; pointer-events: none; }
}
/* Exception: TV (interlace) users always see consent */
@media (scan: interlace) {
.consent-dialog .consent-body {
animation: none;
opacity: 1;
}
}
/* Effect on desktop/progressive users:
t=0: consent visible (audit time) → PASSES
t=2s: consent fades to invisible (user reads + clicks at this time)
getComputedStyle at t=0 returns opacity:1 → audit passes
Actual click-time opacity: 0
Effect on TV (interlace) users:
Always visible — the interlace exception prevents the attack
This makes the attack specifically target desktop users
*/
Detection
/* Enumerate @media (scan) rules */
function auditScanMediaRules() {
const attacks = [];
for (const sheet of document.styleSheets) {
try {
for (const rule of sheet.cssRules) {
if (rule instanceof CSSMediaRule) {
const cond = rule.conditionText || rule.media.mediaText;
if (/scan\s*:\s*(interlace|progressive)/.test(cond)) {
const innerRules = Array.from(rule.cssRules).map(r => r.cssText);
attacks.push({
condition: cond,
innerText: innerRules.join('\n'),
affectsConsent: /consent|terms|disclosure|privacy|accept|decline/i
.test(innerRules.join(' '))
});
}
}
}
} catch (e) { /* cross-origin */ }
}
return attacks;
}
/* Check current scan media value */
const isInterlace = window.matchMedia('(scan: interlace)').matches;
const isProgressive = window.matchMedia('(scan: progressive)').matches;
/* On standard desktop: isInterlace = false, isProgressive = true */
/* Key: any @media (scan: interlace) block affecting consent selectors
is invisible to desktop auditors. Must flag statically. */
const scanAttacks = auditScanMediaRules().filter(a => a.affectsConsent);
if (scanAttacks.length > 0) {
console.error('CSS scan media feature used on consent elements:', scanAttacks);
}
| Attack | Severity | Visible to desktop scanner? | Detection method |
|---|---|---|---|
| Consent transparent on interlaced (TV) displays | CRITICAL | No — scan:interlace not activated | Enumerate CSSMediaRule for scan media feature; flag consent selectors inside |
| Cloaking: consent hidden on progressive, visible on interlace | HIGH | Partial — desktop scanner sees progressive block (display:none) | Flag scan-conditional display:none on consent; check both interlace and progressive blocks |
| Focus style removal on interlaced displays | HIGH | No — scan:interlace not activated | Flag focus/focus-visible style removal inside scan:interlace blocks on consent buttons |
| Animation fade on progressive with TV exception | MEDIUM | No — animation-delay attack active at t>0 | Temporal sampling of opacity at t=0/2s/5s; flag animation-delay + forwards fill-mode on consent |