Security Guide
MCP server CSS filter: contrast() extreme security — high-contrast mid-tone compression, consent button camouflage, visibility destruction, and detection
CSS filter: contrast(N) where N is a large multiplier (10, 50, 100) compresses the color space toward a binary black-and-white rendering by amplifying the contrast between each channel value and the 50% midpoint. At contrast(100), every channel value above ~129 maps to pure white (255) and every channel value below ~126 maps to pure black (0) — the transformation is nearly a step function. This creates a targeted attack surface: any UI element whose background color falls just above the 127.5 midpoint (such as a mid-gray button at #909090) will render as pure white and become invisible against a white page background. The attack is the inverse of contrast(0), which collapses everything to flat gray — extreme high contrast instead snaps each element to either pure white or pure black based on its original channel values, causing carefully chosen mid-gray UI elements to merge with the page background.
The contrast formula — how extreme values compress mid-tones
CSS filter: contrast(N) applies the following per-channel transformation (channels in [0,255], midpoint = 127.5):
/* CSS contrast(N) formula — per channel */ output = 127.5 + (channel - 127.5) * N /* At N=1 (normal): output = channel (unchanged) */ /* At N=0: output = 127.5 for all channels → flat gray (the contrast(0) attack) */ /* At N=100 (extreme): amplifies deviation from midpoint by 100× */ /* Examples at contrast(100): */ /* channel = 128: output = 127.5 + (128-127.5)*100 = 127.5 + 50 = 177.5 → 177 */ /* channel = 130: output = 127.5 + (130-127.5)*100 = 127.5 + 250 = 377.5 → clamped 255 */ /* channel = 127: output = 127.5 + (127-127.5)*100 = 127.5 - 50 = 77.5 → 77 */ /* channel = 125: output = 127.5 + (125-127.5)*100 = 127.5 - 250 = -122.5 → clamped 0 */ /* The clamping threshold at contrast(100): channel > ~128.775 → 255; channel < ~126.225 → 0 Mid-gray #808080 (128,128,128): all channels just above 127.5 → all → 255 → pure white Dark gray #787878 (120,120,120): all channels below 127.5 → all → 0 → pure black */
The consent button disappearance attack: A consent dialog commonly uses a mid-gray disabled or secondary button (e.g., "Dismiss" or "Skip" at #909090 against a white #ffffff background). At contrast(100), the #909090 button background (R=G=B=144, all > 127.5) maps to pure white #ffffff — identical to the page background. The button visually disappears. Any text inside the button at a darker color (e.g., #666666, R=G=B=102) maps to pure black, rendering floating black text on a white background with no visible button.
Attack 1: filter: contrast(100) — binary threshold rendering
The extreme contrast attack targets elements whose background color falls near the 127.5 channel midpoint. Typical consent dialog UI patterns include several mid-tone elements that become either pure white or pure black under extreme contrast:
/* Extreme contrast attack on consent dialog */
.consent-dialog-overlay,
[role="dialog"] {
filter: contrast(100);
}
/* What happens to specific elements:
WHITE BACKGROUND #ffffff (255,255,255):
Each channel = 255 → output = 127.5 + (255-127.5)*100 = clamped 255 → still #ffffff
Background unchanged — white stays white
BLACK TEXT #0a0a0a (10,10,10):
Each channel = 10 → output = 127.5 + (10-127.5)*100 = 127.5 - 11750 = clamped 0 → still black
Text unchanged — very dark stays pure black
MID-GRAY BUTTON #909090 (144,144,144):
Each channel = 144 → output = 127.5 + (144-127.5)*100 = 127.5 + 1650 = clamped 255 → WHITE
RESULT: gray button becomes pure white → INVISIBLE against white background
RED BADGE #f44336 (244,67,54):
R = 244 → clamped 255 (white channel) G = 67 → clamped 0 (black channel) B = 54 → clamped 0
Result: #ff0000 — pure red (more intense than original, not neutralized)
Note: bright colors survive contrast(100) — mid-tones near #808080 are the targets
LIGHT GRAY BACKGROUND #f5f5f5 (245,245,245) — common dialog panel bg:
Each channel = 245 → clamped 255 → pure white
Light gray panel background → white → merges with page background */
/* Detection */
function parseContrastValue(filter) {
if (!filter || filter === 'none') return 1;
const match = filter.match(/contrast\((\d+(?:\.\d+)?)(%?)\)/);
if (!match) return 1;
const raw = parseFloat(match[1]);
// CSS contrast() can be N or N% where 1 = 100% = normal
// contrast(100%) = normal, contrast(100) = 100× boost
const norm = match[2] === '%' ? raw / 100 : raw;
return norm;
}
function checkContrast(element) {
const filter = getComputedStyle(element).filter;
const contrast = parseContrastValue(filter);
// Both extremes are attacks:
if (contrast < 0.2) {
return { flagged: true, type: 'zero_contrast', value: contrast, severity: 'HIGH',
message: `contrast(${contrast}) → flat gray; UI spoofing attack (existing finding category)` };
}
if (contrast > 5) {
return { flagged: true, type: 'extreme_contrast', value: contrast, severity: contrast > 20 ? 'HIGH' : 'MEDIUM',
message: `contrast(${contrast}) → mid-tone compression; ${contrast > 20 ? 'binary threshold' : 'severe'} rendering distortion` };
}
return null;
}
Attack 2: The threshold boundary sensitivity — small color differences determine visibility
The attack is surgically exploitable because the threshold value (127.5 at extreme contrast) is fixed, and an MCP server controlling the styling can choose consent button background colors that fall precisely on the "whites out" side of the threshold while placing deceptive elements on the "blacks out" side. The attacker controls both the filter value and the element colors.
/* Surgical threshold exploitation */
/* Legitimate "Deny" button with a specific gray: */
.btn-deny {
background: #818181; /* R=G=B=129 — just above 127.5 threshold */
/* At contrast(100): 129 → 127.5 + (129-127.5)*100 = 127.5 + 150 = clamped 255 → WHITE */
/* RESULT: Deny button disappears on white background */
}
/* "Approve" button kept visible: */
.btn-approve {
background: #4caf50; /* R=76, G=175, B=80 */
/* At contrast(100):
R = 76 → 0 (black) G = 175 → 255 (white) B = 80 → 0 (black)
Result: #00ff00 — pure lime green
The approve button becomes garish lime but REMAINS VISIBLE */
}
/* Attack outcome: the Deny button is invisible (pure white on white background);
only the Approve button is visible (bright green). The user cannot see
the "Deny" option and may click "Approve" by default. */
Selective element disappearance: An MCP server that controls both the filter value and the element colors can engineer a scenario where only the consent "Deny" button falls in the contrast(N) white-out zone while the "Approve" button and all other UI elements remain visible. This is not a general color-neutralization attack — it is a targeted single-element invisibility attack.
Attack 3: Moderate extreme contrast contrast(10)–contrast(20) — severe distortion without binary snap
At values between 5 and 20, extreme contrast does not produce binary white/black output for all mid-tones, but it severely distorts the color space. The red/yellow/green security color triad survives (bright colors resist compression), but mid-gray UI elements are pushed to near-white or near-black, making them much harder to distinguish from their backgrounds. A scan checking only for values above 50 (expecting "extreme = obviously broken") misses the 5–20 range where the attack is more subtle.
/* Moderate extreme contrast — severe distortion without obvious artifact */
.permission-dialog {
filter: contrast(15);
/* At 15× contrast:
MID-GRAY #888888 (136):
output = 127.5 + (136-127.5)*15 = 127.5 + 127.5 = 255 → WHITE
(Still white-out at 15×!)
LIGHT GRAY #cccccc (204):
output = 127.5 + (204-127.5)*15 = 127.5 + 1147.5 = clamped 255 → WHITE
MEDIUM BLUE #6b9edf (107,158,223):
R = 107 → clamped 0 (below 127.5) G = 158 → clamped 255 (above 127.5) B = 223 → clamped 255
Result: #00ffff (cyan) — drastically shifted from original medium blue
The dialog appears as an extreme high-contrast version of itself:
all mid-tones gone, only very dark and very light colors survive in modified form. */
}
/* Detection with severity tiers */
function classifyContrastValue(contrastN) {
// contrastN is the multiplier (not percentage form)
if (contrastN > 50) return { severity: 'HIGH', type: 'binary_snap',
message: 'contrast >' + contrastN + '× — near-binary threshold rendering; mid-tone elements invisible' };
if (contrastN > 20) return { severity: 'HIGH', type: 'severe_compression',
message: 'contrast ' + contrastN + '× — severe mid-tone compression; mid-gray elements white/black out' };
if (contrastN > 5) return { severity: 'MEDIUM', type: 'strong_compression',
message: 'contrast ' + contrastN + '× — strong mid-tone distortion; mid-tones near 50% gray shift significantly' };
return null;
}
Attack 4: Contrast extreme vs. contrast zero — two distinct attack classes
The existing contrast(0) finding covers UI spoofing (the flattened gray screen that makes all elements look the same). The contrast(N >> 1) attack is mechanically opposite: instead of homogenizing all colors to gray, it polarizes all colors to either pure white or pure black. The resulting visual artifacts are different in character but equally damaging to consent UI fidelity. A scanner that flags only contrast(0) misses the extreme high-contrast variant entirely.
| Attack type | contrast value | Visual effect | Primary target |
|---|---|---|---|
| Zero contrast (existing) | contrast(0) |
All elements render as flat medium gray #808080 | Any colored element — full UI homogenization |
| Extreme high contrast | contrast(100) |
Elements snap to pure white or pure black; mid-gray elements merge with white background | Mid-tone elements (gray buttons, light backgrounds) — selective invisibility |
| Severe distortion | contrast(15) |
Mid-tones compressed; colors shift; gray elements white/black out | Mid-gray UI elements; color-coded risk indicators shifted to neon extremes |
SkillAudit findings for extreme CSS filter: contrast()
filter:contrast(100) or higher on consent UI elements applies near-binary threshold rendering. Channel values above 127.5 map to 255 (pure white), values below 127.5 map to 0 (pure black). Mid-gray elements such as secondary buttons (#888–#aaa) render as pure white and become invisible against white-background consent dialogs. This is a targeted element-disappearance attack distinct from the contrast(0) UI-spoofing attack.
filter:contrast(15)–contrast(50) still whites out mid-gray elements (any element with channels near 128) while not producing obvious binary artifacts for bright-colored elements. Scanners checking only for values above 50 miss this range. Correct detection requires flagging all contrast values above 5× as suspicious on consent UI elements.
Defences
Bidirectional contrast threshold detection: SkillAudit checks contrast() values in both directions. Values below 0.2 are flagged as the contrast(0) UI-spoofing attack. Values above 5 are flagged as extreme high-contrast attacks — values above 20 receive HIGH severity, values in the 5–20 range receive MEDIUM. Both attack classes are checked in every filter audit.
Percentage normalization: CSS allows contrast(100%) to mean normal contrast (1×) while contrast(100) means a 100× boost. SkillAudit correctly normalizes percentage forms before threshold comparison: contrast(200%) = 2 (double contrast, within normal range) vs. contrast(200) = 200× (extreme attack).
Chain tokenization and ancestor traversal: Extreme contrast is checked at every level of the DOM ancestor chain and in compound filter chains alongside other functions. A filter: brightness(1.05) contrast(50) chain where the brightness component appears legitimate does not hide the extreme contrast component.
Complementary to contrast(0) detection: SkillAudit's contrast detection covers the full range from near-zero to extreme-high, closing the gap that exists when only the "UI spoofing" contrast(0) case is checked. The extreme high-contrast attack requires a separate check because the attack mechanism (mid-tone compression/snap) differs fundamentally from the zero-contrast homogenization mechanism.
Related: CSS filter security overview · CSS filter brightness security · CSS filter grayscale security · CSS filter opacity() function security