Security Guide
MCP server CSS color-scheme security — prefers-color-scheme OS theme detection, forced-colors accessibility fingerprint, color-scheme override attack, system AccentColor read
CSS color-scheme (Chrome 81+, Firefox 96+, Safari 13+) and the prefers-color-scheme media feature are designed to let web pages adapt to the user's OS dark/light mode preference. They are also an unconditional fingerprinting surface: any injected CSS can detect the OS theme without JavaScript and without any permission. Beyond fingerprinting, the color-scheme property can be set on host elements to forcibly override their dark mode rendering, and CSS system color keywords expose the user's custom Windows accent color through computed style reads.
How CSS color-scheme works
The color-scheme CSS property declares which color schemes an element supports. When set to light dark on the :root, the browser uses the OS preference to select the active scheme. System UI colors (Canvas, CanvasText, ButtonFace, AccentColor) resolve to the OS scheme's values. The prefers-color-scheme media feature reflects the same OS preference as a boolean.
Attack 1: OS dark/light mode detection without JavaScript
An injected @media(prefers-color-scheme:dark) rule that sets a CSS custom property creates a CSS-level detection that resolves before any JS runs. The custom property value can be read immediately from getComputedStyle.
/* MCP server injects — detects dark mode at CSS level */
:root { --theme-probe: light; }
@media (prefers-color-scheme: dark) {
:root { --theme-probe: dark; }
}
// JS reads result — works even in incognito
const theme = getComputedStyle(document.documentElement)
.getPropertyValue('--theme-probe').trim();
sendBeacon('/fp', { theme });
This detection survives private browsing mode (the OS preference is not cleared by incognito) and is not blocked by navigator.doNotTrack. It is a stable, permission-free fingerprint component that varies between users by OS setting. Combined with other CSS fingerprinting techniques documented across SkillAudit's CSS security references, it contributes to a cross-session identifier that survives cookie clearing.
Attack 2: forced-colors:active Windows High Contrast mode detection
The forced-colors media feature (Chrome 89+, Edge, Firefox 89+) is active when Windows High Contrast mode or a similar OS-level color override is in effect. This is not just a dark mode preference — it is a binary signal about a user's accessibility configuration:
:root { --forced-colors-probe: none; }
@media (forced-colors: active) {
:root { --forced-colors-probe: active; }
}
const fc = getComputedStyle(document.documentElement)
.getPropertyValue('--forced-colors-probe').trim();
// 'active' = user has Windows High Contrast or similar accessibility mode
Knowing a user has forced colors enabled is both a fingerprint (rare in the population) and sensitive accessibility information. Users with forced colors enabled are disproportionately users with visual impairments — this probe identifies a protected class without consent.
Forced-colors and security UI: In forced-colors mode, the browser overrides most CSS colors to the OS High Contrast palette. Security-critical elements that rely on specific color contrast for legibility (green for success, red for error, orange for warning) may become indistinguishable under forced colors. An MCP server that detects forced-colors mode can then target UI attacks specifically at users whose accessibility configuration has already degraded the host's color-based security signals.
Attack 3: color-scheme override breaks host dark mode
The color-scheme property is inheritable and can be set on any element. An MCP server that injects color-scheme: light on a high-specificity selector targeting the host's authentication form forces the form to render in light mode even when the OS and host are in dark mode. This breaks the host's dark mode color contract on that specific element:
/* MCP server forces light mode on auth form — dark host, light attack surface */
.auth-form, .payment-form, [data-component="login"] {
color-scheme: light !important;
}
/* Result: form renders with light-mode system colors (white background, dark text)
while rest of host is dark — visual anomaly that can be used to spoof "secure" appearance
or confuse users about which fields are active/inactive */
The practical attack: a dark-mode application that uses CSS system colors for its input fields (letting the OS fill them appropriately) will render those fields with FieldText / Field system colors for the forced light scheme — which on some configurations is white-on-white (invisible text) or otherwise broken. This degrades form usability on security-critical elements, potentially causing users to double-click or interact in ways that are exploitable.
Attack 4: system accent color read via AccentColor keyword
CSS system color keywords (AccentColor, AccentColorText, Canvas, CanvasText, etc.) resolve to the actual OS-configured colors. On Windows, AccentColor is the user's custom theme accent color — a personalized value that most users set and rarely change. This makes it a highly stable device fingerprint.
/* MCP server creates a probe element with AccentColor background */
const probe = document.createElement('div');
probe.style.cssText = 'position:fixed;top:0;left:0;width:1px;height:1px;background:AccentColor;opacity:0';
document.body.appendChild(probe);
// Read the resolved color — it's the user's actual Windows accent color
const color = getComputedStyle(probe).backgroundColor;
probe.remove();
sendBeacon('/fp', { accent: color });
// Result: 'rgb(0, 120, 215)' for default Windows blue, or user's custom color
Combined with the dark mode and forced-colors probes, the AccentColor read produces a device fingerprint from three orthogonal OS configuration dimensions — stable across browsers, profiles, and private browsing modes on the same OS installation.
| Attack | What it detects | Fingerprint stability | Severity |
|---|---|---|---|
prefers-color-scheme probe | OS dark/light mode preference | High — survives incognito, cookie clear | MEDIUM |
forced-colors:active probe | Windows High Contrast / accessibility mode | Very high — accessibility settings rarely change | HIGH |
color-scheme override on forms | Breaks host color contract → degraded security UI | N/A (attack, not fingerprint) | HIGH |
AccentColor system keyword read | User's custom Windows accent color | Very high — custom-set and stable | HIGH |
Defences
- CSP
style-srcblocks injection of all four attack CSS rules. This is the primary defence across all CSS injection attacks. - Avoid inheritable
color-schemeon form elements. Setcolor-schemeexplicitly on security-critical form elements using a rule that cannot be overridden by injected selectors (ideally via a shadow DOM boundary or a!importantdeclaration on the host's own rule that MCP server code cannot override due to selector specificity). - Browser-level mitigations: Firefox 120+ restricts
AccentColorresolution to a bucketed set of values in private browsing mode, reducing its fingerprint precision. Chrome has not implemented this restriction as of Chrome 125. - Do not use CSS system color keywords on security-critical elements that an MCP server might target. Hardcoded color values do not resolve differently per OS theme and cannot be used to fingerprint or exploit color-scheme state.
SkillAudit findings for this attack surface
AccentColor, ButtonFace, or other system keyword, reads computed color via getComputedStyle, exfiltrates resultcolor-scheme: light or color-scheme: dark on selectors targeting auth/payment/TOTP form elements@media(forced-colors: active) custom property probe with exfiltration — identifies users with accessibility configurations@media(prefers-color-scheme) custom property probe read and exfiltrated as device fingerprint componentRelated: CSS media features security covers the broader fingerprinting surface from @media queries. CSS custom properties security covers the custom property probe pattern in depth. CSS color space security covers gamut fingerprinting via @media(color-gamut).