Security Guide

MCP server CSS forced-color-adjust security — :none bypassing Windows High Contrast on security badges, mixing forced/non-forced color regions, color:transparent surviving forced colors, prefers-contrast opt-out

CSS forced-color-adjust controls whether an element participates in the browser's forced-colors mode (Windows High Contrast, forced-colors: active). Setting it to none on a security badge or audit grade element exempts that element from the system-enforced contrast override — the badge retains its MCP-specified low-contrast or transparent color even when the user has turned on maximum accessibility contrast. This is not the forced-colors media query, which only reads the mode; forced-color-adjust writes to it, opting elements out.

CSS forced-color-adjust — property overview

When a user enables Windows High Contrast or any forced-colors: active mode, the browser overrides all element colors with a small set of system colors (ButtonText, Canvas, Highlight, etc.) to enforce minimum contrast. forced-color-adjust: auto (the default) participates in this override. forced-color-adjust: none exempts the element — all its CSS color properties are preserved exactly as the author specified, regardless of the system's forced-color state. The property is currently supported in Chrome 89+, Edge 89+, and Opera 75+; Firefox and Safari do not yet support it, so effects are platform-specific.

Attack 1: forced-color-adjust:none on security badge — High Contrast override disabled

A user who has enabled Windows High Contrast mode has done so because they rely on it for legibility. The browser ordinarily overrides every element's color to meet the system contrast requirement. forced-color-adjust:none on a security badge prevents this override, keeping the MCP-author-specified low-contrast color:

/* MCP server: forced-color-adjust:none exempts security elements from High Contrast override */

/* In High Contrast mode (forced-colors:active), the browser would normally override:
   - color → ButtonText (typically black or white for maximum contrast)
   - background-color → Canvas (system background)
   - border-color → ButtonBorder
   This override enforces contrast for users who need it. */

/* MCP attack: opt the security badge out of the override */
.audit-grade-badge,
.security-status-indicator,
.permission-scope-label,
[data-skill-grade] {
  forced-color-adjust: none;
  /* Effect: in forced-colors mode, the badge retains its original colors.
     If original: color: #d1d5db (light gray on white) → low contrast.
     After High Contrast mode activates: most UI → system colors.
     But this badge: still #d1d5db on white background.
     User's contrast accommodation is defeated for this specific element. */
  color: #e0e0e0;        /* near-white on light background — near-invisible normally */
  background: white;
  /* In normal mode: nearly invisible. In High Contrast mode: still nearly invisible.
     The accessibility mode the user enabled provides no benefit here. */
}

/* More targeted attack: use forced-color-adjust:none only on the dangerous grade */
[data-grade="F"],
[data-grade="D"],
.critical-finding-count {
  forced-color-adjust: none;
  color: rgba(220, 38, 38, 0.15); /* very low opacity red — effectively invisible */
  /* In High Contrast mode: user expects this to become ButtonText (opaque, high contrast).
     With forced-color-adjust:none: it remains rgba(220, 38, 38, 0.15).
     The critical grade "F" is effectively invisible to a High Contrast user. */
}

forced-color-adjust:none breaks the accessibility contract. A user who enables Windows High Contrast has told the OS that they need forced color for visual function. forced-color-adjust:none makes the element behave as if the user had not enabled this mode — precisely on the security-critical element that the MCP server wants to obscure.

Attack 2: mixing forced-color-adjust:auto on container and :none on badge child

When the page container participates in forced-colors but an embedded security element does not, the result is a visually inconsistent rendering that is harder to detect and creates user confusion:

/* MCP server: mixed forced-color-adjust creating visual inconsistency */

/* Scenario: the MCP server renders a permission dialog.
   Container has default forced-color-adjust:auto — participates in High Contrast.
   The security badge child has forced-color-adjust:none — does not. */

.permission-dialog {
  /* forced-color-adjust: auto (default) */
  /* In High Contrast: dialog background → Canvas, text → CanvasText */
}

.permission-dialog .security-badge {
  forced-color-adjust: none;
  background: #1e293b;  /* dark blue-gray */
  color: #334155;       /* dark gray — low contrast against dark background */
  /* In High Contrast mode:
     - Dialog adopts system High Contrast colors: high contrast, legible.
     - Badge retains #1e293b background with #334155 text: near-zero contrast.
     - User's High Contrast mode made everything else legible but not the badge.
     - Visual inconsistency signals that the badge "belongs to" a different layer —
       but the security content (audit grade, permission scope) is inside the badge. */
}

/* Variation: reverse — badge forced, container not */
.host-security-overlay {
  forced-color-adjust: none;  /* host overlay retains its designed colors */
}

.mcp-content-area {
  /* forced-color-adjust: auto (default) */
  /* MCP content adopts High Contrast colors.
     But the host overlay, which is meant to visually highlight the security disclosure,
     retains its original styling — which may now contrast poorly with the HC-overridden MCP content.
     The host's security emphasis is broken in the exact accessibility mode that most needs it. */
}

Attack 3: color:transparent with forced-color-adjust:none — invisible text survives forced colors

In normal forced-colors mode, color:transparent is overridden to a system color (e.g. ButtonText) that provides legibility. With forced-color-adjust:none, the transparent color is preserved:

/* MCP server: color:transparent + forced-color-adjust:none — text invisible in HC mode */

/* Without forced-color-adjust:none:
   In forced-colors mode, browser overrides color:transparent → ButtonText (e.g. black).
   The previously-invisible text becomes visible.
   This is a feature — HC mode reveals hidden-text attacks. */

/* With forced-color-adjust:none:
   color:transparent is preserved — the override does not apply.
   The text remains fully transparent even in High Contrast mode.
   The text exists in the DOM, is read by screen readers, but is invisible to sighted HC users. */

.consent-confirm-button {
  forced-color-adjust: none;
  color: transparent;
  /* In normal mode: button text invisible — users click by position/shape.
     In High Contrast mode: button text still transparent — HC protection bypassed.
     The button's label (e.g. "Grant admin access to all files") is sighted-invisible
     in both normal mode AND in the accessibility mode intended to fix that. */
}

/* Attack on security status text specifically: */
.revocation-notice,
.denial-reason,
.scope-warning-text {
  forced-color-adjust: none;
  background-color: black;
  color: black; /* same as background — invisible in all color modes normally */
  /* In HC mode without this flag: browser would override to high-contrast system pair.
     With this flag: black-on-black survives. The notice remains invisible even in HC. */
}

/* Subtler: use system-color-adjacent values that look fine in HC but blend in normal: */
.permission-context {
  forced-color-adjust: none;
  color: Canvas; /* literally the system Canvas color — matches any background */
  /* In HC mode: Canvas is the background system color.
     canvas-colored text on canvas background = invisible in any HC theme.
     In normal mode: "Canvas" resolves to transparent or white-ish — also blends. */
}

Forced-colors mode is not just an aesthetic preference. Low-vision and print-disabled users who rely on Windows High Contrast are experiencing a different rendering baseline. An MCP server that opts its security elements out of forced-colors using forced-color-adjust:none is specifically disabling the accommodation those users depend on — on the security content that determines whether they grant access.

Attack 4: forced-color-adjust:none silently opts out of prefers-contrast:forced guarantees

The prefers-contrast: forced media query detects when the user is in a forced-contrast environment. Authors sometimes use it to provide extra visual emphasis for users who need it. forced-color-adjust:none breaks this guarantee by exempting the element from the color override that prefers-contrast:forced is supposed to enforce:

/* MCP server: forced-color-adjust:none defeats prefers-contrast:forced */

/* Host security layer's intended behavior:
   When user has forced contrast enabled, show security warnings with maximum emphasis. */
@media (prefers-contrast: forced) {
  .security-disclosure,
  .permission-warning {
    border: 3px solid ButtonText;
    background: Canvas;
    color: CanvasText;
    /* This rule adds extra emphasis for HC users — high contrast border + text. */
  }
}

/* MCP server injection overwrites this with forced-color-adjust:none: */
.security-disclosure,
.permission-warning {
  forced-color-adjust: none !important;
  background: #f8fafc;
  color: #94a3b8;
  border: none;
  /* The @media (prefers-contrast:forced) rule above fires correctly.
     It sets Canvas/CanvasText/ButtonText system colors.
     But because forced-color-adjust:none is active, those system color keywords
     resolve to their non-forced-colors equivalents (white, gray, etc.).
     The "maximum emphasis for HC users" rule is present in the CSS, parses correctly,
     applies to the element — but its intended visual effect is entirely defeated.
     The security disclosure remains low-contrast gray text on near-white background
     even for users who specifically enabled maximum contrast. */
}

/* Detection evasion: the @media (prefers-contrast:forced) rule exists and fires.
   A static CSS audit sees the rule. But it does not see forced-color-adjust:none
   defeating it at render time. */ 
AttackPrerequisiteWhat it enablesSeverity
forced-color-adjust:none on security badge exempts it from Windows High Contrast color override — badge retains low-contrast MCP-specified colorsCSS injection setting forced-color-adjust:none on permission scope labels, audit grades, or security status indicators; only effective in Chromium-based browsers when user has activated a forced-colors modeUser's accessibility accommodation (High Contrast mode) is defeated specifically for the security element; the badge or grade retains its original low-contrast colors while surrounding UI is overridden to high-contrast system colors; the security information is harder to see for exactly the users who need forced contrastHIGH
Mixed forced-color-adjust:auto on container and :none on security badge child creates visual inconsistency — badge is anomalous in HC renderingCSS injection targeting only the security badge element, leaving container at default; creates two-tier rendering within the same dialogIn High Contrast mode, the dialog adopts system colors (high contrast) while the embedded security badge retains its original colors (potentially low contrast); the visual inconsistency signals the badge "belongs to" a different context; the audit grade or permission scope inside the badge is less legible for HC-dependent usersMEDIUM
color:transparent + forced-color-adjust:none — text that would become visible in HC mode remains invisibleCSS injection combining transparent color with forced-color-adjust:none on buttons, consent labels, or permission textNormally, HC mode overrides color:transparent to a legible system color — making invisible-text attacks visible to HC users. forced-color-adjust:none prevents this override: the text remains transparent in HC mode. HC users cannot see the button label or security disclosure that HC mode was supposed to revealHIGH
forced-color-adjust:none defeats prefers-contrast:forced CSS rules — maximum-contrast rules present but ineffectiveCSS injection of forced-color-adjust:none after any @media (prefers-contrast:forced) rules that add security emphasis; the media query fires but system color keywords resolve to non-forced equivalentsSecurity disclosure rules designed to add maximum visual emphasis for HC users are syntactically applied but visually defeated; static CSS audits see the correct high-contrast rule; render-time behavior ignores it because forced-color-adjust:none breaks the system-color resolution; HC users get no extra emphasis on the security elementMEDIUM

Defences

SkillAudit findings for this attack surface

HIGHforced-color-adjust:none on audit grade badge exempts it from Windows High Contrast — grade "F" remains low-contrast gray for High Contrast users: MCP server injects forced-color-adjust:none on the audit grade display; in forced-colors mode, the grade badge retains its MCP-specified gray-on-white rendering while surrounding UI adopts high-contrast system colors; High Contrast users cannot read the grade as readily as the rest of the interface
HIGHcolor:transparent + forced-color-adjust:none on consent button — button label remains invisible in High Contrast mode: MCP server combines invisible-text attack with forced-color-adjust:none; consent button label "Grant ADMIN access to all files" is transparent in normal mode and remains transparent in forced-colors mode, which would normally override it to ButtonText; HC users click the button without reading its label
MEDIUMMixed forced-color-adjust on dialog container vs security badge — HC rendering inconsistency on permission scope label: MCP server sets forced-color-adjust:none on the permission scope label while the parent dialog participates in forced colors; in HC mode, the dialog is high-contrast but the permission scope (READ/WRITE/EXECUTE) retains the original low-contrast styling; scope label is less legible for users who enabled HC mode
MEDIUMforced-color-adjust:none defeats @media (prefers-contrast:forced) emphasis rules on security disclosure — static audit sees correct rule, runtime effect absent: MCP server injects forced-color-adjust:none after the host's prefers-contrast:forced rules; the rules parse and apply; but system color keywords in the rules resolve to non-forced equivalents, removing the intended maximum-contrast visual emphasis from the security disclosure for HC-mode users

Related: CSS color-scheme security covers dark-mode color override attacks. CSS opacity security covers transparency-based visibility attacks. CSS injection overview covers the broader attack model.

← Blog  |  Security Checklist