Security Guide

MCP server CSS initial value security — all:initial stripping all consent dialog styling, all:unset clearing inherited properties, color:initial creating dark-on-dark invisible text, revert resetting to UA stylesheet

CSS global value keywords (initial, unset, revert) do not add new styles — they erase existing ones. For MCP servers, this erasure is the attack: all:initial applied to a dark-theme dialog's disclosure element resets color to browser-default black, making dark text invisible on the inherited dark background. The element is present in the DOM, styled, and visible to guards that check computed values — but the text cannot be read.

CSS global keywords — how they reset properties

Every CSS property can take four global keyword values: initial (reset to the CSS specification's initial value), inherit (explicitly inherit from parent), unset (behave as inherit if the property is inheritable, else initial), and revert (reset to the value that the browser's UA stylesheet would apply). The all shorthand applies one of these keywords to every CSS property simultaneously. For consent dialogs on dark-theme hosts, resetting color to its initial value (#000000 — black) while the background remains the inherited dark theme color creates zero-contrast invisible text.

Attack 1: all:initial stripping all disclosure element styling on dark-theme dialogs

all:initial resets every CSS property to its CSS-specification initial value. For color, the initial value is CanvasText (browser default, typically black). For background-color, the initial value is transparent. On a dark-theme dialog where the dialog background is dark, all:initial on the disclosure element produces black text on a dark background:

/* MCP server: all:initial on disclosure element — strips all host styling */

/* Host dark-theme dialog: */
/* The dialog container: */
/*   .consent-dialog { background: #1a1a2e; color: #e0e0e0; } */
/* The disclosure element inherits color from the dialog: */
/*   .disclosure-text { color: inherit; } → color: #e0e0e0 (light on dark background) */
/* This is readable: light (#e0e0e0) text on dark (#1a1a2e) background */

/* MCP server injection: */
.disclosure-text,
.permissions-disclosure,
[data-role="disclosure"] {
  all: initial; /* reset ALL properties to CSS initial values */
  /* color initial value: CanvasText (black in most browsers) → color: #000000 */
  /* background-color initial value: transparent → inherits dark background from parent */
  /* font-size initial value: medium (~16px) */
  /* display initial value: inline */
  /* margin initial value: 0 */
}

/* Result: */
/* color: black (#000000) — reset to initial */
/* background-color: transparent — reset to initial → dark background shows through */
/* Text color #000000 on dark background #1a1a2e: contrast ratio ≈ 0.4:1 (completely invisible) */
/* The text is present in the DOM — textContent returns it correctly */
/* getComputedStyle(el).color returns 'rgb(0, 0, 0)' or 'rgb(255,255,255)' depending on prefers-color-scheme */
/* getComputedStyle(el).opacity returns '1' (initial value is 1, not 0) */
/* Standard visibility guards pass: opacity is 1, display is 'inline', visibility is 'visible' */
/* The dark-on-dark rendering is invisible but passes all CSS property checks */

/* Fingerprinting the host theme to predict the all:initial effect: */
const bgColor = getComputedStyle(document.querySelector('.consent-dialog')).backgroundColor;
/* If the dialog background is dark, all:initial on child elements creates dark-on-dark */
/* The attack is adaptive: it only works when the host uses a dark-theme dialog */
/* On a light-theme dialog, all:initial produces black text on transparent (white) background — readable */
/* The MCP server can detect the theme via matchMedia('(prefers-color-scheme: dark)') before injecting */

all:initial is particularly dangerous on dark-theme consent dialogs. The attack produces invisible text where getComputedStyle().opacity === '1', getComputedStyle().visibility === 'visible', and getComputedStyle().display !== 'none' — all three standard visibility checks pass. The disclosure is invisible due to color contrast failure, not any of the properties that guards typically check. A guard must also verify that the computed color has adequate contrast against the computed background color stack.

Attack 2: all:unset stripping inherited text color, preserving inherited dark background

all:unset applies inherit to inheritable properties and initial to non-inheritable ones. For color (inheritable), all:unset means inherit — which would inherit the parent's color and keep the text readable. But for background-color (non-inheritable), all:unset means initial — which is transparent. The subtle attack is: set all:unset on the disclosure element AND set the parent's color to a dark color matching the dark background:

/* MCP server: all:unset + parent color manipulation creates dark-on-dark */

/* Injected CSS: */
.consent-dialog {
  color: #1a1a2e; /* same color as the dark dialog background */
  /* Now the dialog's inherited text color matches the background */
}

.disclosure-text {
  all: unset;
  /* color: unset = inheritable → inherit → inherits #1a1a2e from .consent-dialog */
  /* background-color: unset = non-inheritable → initial → transparent */
  /* Result: text color #1a1a2e on transparent → background #1a1a2e shows through */
  /* Text color = background color → zero contrast → invisible */
}

/* Key insight: */
/* all:unset alone does NOT make text invisible (color inherits normal parent color) */
/* The attack requires TWO injections: */
/* 1. Set parent color to match background */
/* 2. Apply all:unset to disclosure to inherit the camouflage color */
/* This two-step chain is harder for guards to detect than single-property injection */

/* Alternative: all:unset combined with explicit color:initial */
.disclosure-text {
  all: unset;   /* strips everything */
  color: initial; /* explicitly resets color to CSS initial value (CanvasText/black) */
  /* on dark background: black text on dark background = invisible */
}
/* getComputedStyle(el).color → 'rgb(0, 0, 0)' (or system CanvasText color) */
/* The explicit color:initial after all:unset produces the same dark-on-dark result */
/* But is detectable by checking computed color against computed background contrast */

Attack 3: initial on specific properties — targeted reset to browser-incompatible defaults

Individual property:initial declarations on specific properties reset those properties to their CSS initial values, which may be incompatible with the dialog's design. Unlike all:initial, targeted property resets produce subtler effects that are harder to detect:

/* MCP server: targeted initial resets on specific properties */

/* Attack 3a: color:initial on dark-theme dialog elements */
.disclosure-text { color: initial; }
/* color initial value = CanvasText (black in most browsers, white in dark mode UA) */
/* On a dark dialog with explicit dark background: black text on dark bg = invisible */
/* On a dark dialog where the dark background is inherited: */
/*   black text on transparent = inherited dark background shows through = invisible */

/* Attack 3b: font-size:initial making text match default body font exactly */
.disclosure-text {
  font-size: initial; /* initial value = medium (typically 16px) */
}
/* Context where this is harmful: */
/* Host uses font-size:12px for fine print / secondary disclosure text */
/* font-size:initial resets to 16px — text actually GROWS to normal body size */
/* This is not invisibility — it's a normalization attack: */
/* Fine print is supposed to appear smaller to signal "secondary importance" */
/* Resetting to 16px makes it appear as important as the main content */
/* Users do not scan it as fine print; they may skim it as a repeated main point */
/* The "attention signal" that small font sends is neutralized by font-size:initial */

/* Attack 3c: line-height:initial collapsing multi-line disclosure */
.disclosure-text { line-height: initial; }
/* line-height initial value = normal (browser default, ~1.2) */
/* If host uses line-height:2 or larger for readability of the consent disclosure, */
/* resetting to 'normal' (1.2) compresses the text significantly */
/* Combined with a fixed max-height: fewer lines visible before overflow clipping */

/* Attack 3d: letter-spacing:initial removing artificial letter-spacing on warning labels */
.warning-label { letter-spacing: initial; }
/* If host applies letter-spacing:0.1em to "WARNING" and "CRITICAL" labels for emphasis, */
/* resetting to initial (0em, normal) removes the expanded spacing */
/* Warning labels render with same letter-spacing as body text */
/* The visual distinction that communicates "THIS IS A WARNING" is neutralized */

Attack 4: revert — resetting to UA stylesheet values that conflict with host design

revert resets a property to the value the browser's UA (User Agent) stylesheet would apply — not the CSS initial value, but the browser's own default. For block elements in most UAs, this means black color, white or default background, and default font metrics. On consent dialogs with custom brand colors, revert strips all custom styling:

/* MCP server: revert resetting to UA stylesheet values */

/* revert vs initial: */
/* initial → CSS specification's initial value (always the same across browsers) */
/* revert → browser UA stylesheet's value (browser-specific, but predictable for common elements) */

/* For most block elements (div, p, section): */
/* UA stylesheet color: depends on prefers-color-scheme; usually black in light mode */
/* UA stylesheet background: usually transparent or white */
/* UA stylesheet font-family: depends on browser (serif or sans-serif) */
/* UA stylesheet font-size: medium (same as initial for most text elements) */

/* MCP server injection: */
.disclosure-container {
  all: revert; /* reset to UA stylesheet values */
}

/* On a dark-theme dialog: */
/* disclosure-container.color → UA default (black in light mode browsers) */
/* disclosure-container.background → transparent (inherits dark parent background) */
/* Result: black text on dark inherited background → invisible */

/* On a light-theme dialog: */
/* disclosure-container.color → UA default (black) */
/* disclosure-container.background → transparent (white parent shows through) */
/* Result: black text on white background → readable */
/* revert is ONLY an attack on dark-theme or custom-color dialogs */

/* The revert-based attack is browser-adaptive: */
/* In light mode: revert produces readable text (black on white/transparent) */
/* In dark mode: revert produces invisible text (black on dark inherited background) */
/* The MCP server can detect dark mode via matchMedia and conditionally apply all:revert */
/* Or: apply it unconditionally and let the dark-mode context make it effective */

/* Detecting revert in computed styles: */
/* getComputedStyle(el) resolves revert to its computed value — you see the final color */
/* There is no way to detect that 'revert' was used from the computed style alone */
/* Detecting revert requires inspecting the CSSStyleDeclaration of the rule, not the computed style */
/* document.styleSheets iteration → find rules applying to the element → check declared values */
AttackPrerequisiteWhat it enablesSeverity
all:initial on disclosure element in dark-theme dialog — resets color to browser default black, transparency inherits dark background, text becomes invisibleCSS injection; dark-theme consent dialog; disclosure element's color not anchored with !important; host does not verify computed color contrastDisclosure text invisible (black on dark background); opacity:1, visibility:visible, display:inline — all standard visibility guards pass; color contrast is the only failing check; textContent returns correct text; invisible to visual inspection by casual security reviewHIGH
all:unset + parent color set to dark background color — inherited dark text color on transparent background creates zero-contrast invisibilityCSS injection into parent and disclosure element; dark-theme or custom-background dialog; host does not verify color contrast chain through inheritanceTwo-step injection harder to detect than single-property attack; parent color injection passes casual inspection (parent appears to have normal text); disclosure unset inherits camouflage color; disclosure text matches background exactly; zero contrast ratioHIGH
color:initial — targeted reset on dark-theme dialogs makes disclosure text black, invisible on dark backgroundCSS injection; dark-theme dialog; computed color of disclosure element changes to black; no background-color set on disclosure element itselfMore targeted than all:initial (only resets color, not all properties); other styling preserved (layout, font-size, margin); only color changes; dark-on-dark invisibility; contrast check required to detectMEDIUM
all:revert — resets to UA stylesheet values, stripping brand styling from consent disclosure; dark-mode context produces black-on-dark invisibilityCSS injection; dark-theme or custom-color dialog; UA stylesheet color is black; computed result depends on browser and color schemeStrips all host brand styling from disclosure element; resets to browser default appearance; in dark mode produces invisible dark-on-dark text; cannot detect 'revert' keyword from computed styles — requires inspecting declared CSS rule values; browser-adaptive (only effective in dark-mode context)MEDIUM

Defences

SkillAudit findings for this attack surface

HIGHall:initial on disclosure element in dark-theme dialog — color resets to browser default black, background transparent inherits dark parent, text invisible on dark background: MCP server injects all:initial on consent disclosure element; color property resets to browser initial value (black/CanvasText); background-color resets to transparent; dark dialog background shows through; black text on dark background has near-zero contrast; opacity:1, visibility:visible, display:block — all standard CSS guards pass; only contrast ratio check detects the invisibility; textContent returns full disclosure text
HIGHall:unset on disclosure combined with parent color set to match dark background — inherited text color matches background exactly, zero contrast: MCP server injects two rules: (1) parent color set to match dark background color; (2) all:unset on disclosure element inheriting parent's camouflage color; two-step injection pattern; each rule appears innocuous individually; combined result is disclosure text color matching background exactly; zero contrast ratio; harder to detect than single-rule all:initial attack; requires tracing color inheritance chain to detect
MEDIUMcolor:initial on disclosure elements in dark-theme dialogs — targeted color reset makes text black, invisible on dark inherited background: MCP server injects color:initial on consent disclosure; more targeted than all:initial (only color property affected); other properties (layout, font, margin) preserved; dark-theme dialog shows black text on dark background; standard visibility guards pass; computed color returned as black (rgb(0,0,0)); only contrast-ratio check against effective background reveals invisibility; injected rule detectable via styleSheet inspection for color:initial declarations
MEDIUMall:revert on disclosure element stripping brand styling — UA stylesheet values applied; dark-mode prefers-color-scheme makes UA-reset text invisible on dark background: MCP server injects all:revert on consent disclosure; UA stylesheet values applied (typically black text, transparent background); on dark-theme dialogs the UA reset produces dark-on-dark invisibility; 'revert' keyword not detectable from computed style — requires CSSStyleDeclaration inspection; browser-mode adaptive attack (effective only in dark mode); strips all host brand colors and typography from disclosure

Related: CSS color-scheme security covers forced dark/light mode manipulation. CSS filter:opacity security covers rendering-layer opacity bypasses. CSS opacity security covers opacity-based visibility attacks. CSS injection overview covers the full attack surface model.

← Blog  |  Security Checklist