MCP server CSS radial-gradient() security: white ellipse overlay covers consent center, background-clip:text transparent radial gradient, repeating-radial-gradient concentric ring obstruction, and JS mousedown radial injection

Published 2026-08-07 — SkillAudit Research

The CSS radial-gradient() function creates a gradient that radiates from a focal point outward in an elliptical or circular pattern. Like linear-gradient(), radial gradients are background-image values that can be applied to any element, including pseudo-elements. The key distinction from linear gradients is the center-outward geometry: a radial gradient can be used to create a "spotlight" effect with a white ellipse at the center that precisely targets the key consent clause of an MCP installation dialog, leaving only the periphery of the consent visible while the center is obscured.

The repeating-radial-gradient() variant creates concentric circles or ellipses that repeat at defined intervals. This creates a dense ring pattern that obstructs text across the entire consent element — distinct from the linear-gradient stripe pattern, which creates horizontal bands, and distinct from the center-spotlight pattern of a single radial gradient. See also CSS linear-gradient() attacks, CSS conic-gradient() attacks, and CSS background-image attacks.

Radial vs linear gradient attacks — key distinction: A linear gradient creates bands along a straight axis — useful for fading consent from top to bottom. A radial gradient creates a center-outward pattern — useful for creating a "spotlight" white ellipse that covers the key consent text at the center of the element, leaving the surrounding area visible as apparent context. The surrounding visible text (typically generic boilerplate) gives the false impression that the consent is fully readable. The concentric ring pattern from repeating-radial-gradient() is visually distinctive and can masquerade as a textured or watermarked background.

Attack 1: radial-gradient white ellipse overlay — key consent clause obscured at center (SA-CSS-RGRAD-001)

A pseudo-element overlay over the consent disclosure uses background: radial-gradient(ellipse 80% 60% at center, rgba(255,255,255,0.98) 0%, rgba(255,255,255,0.98) 45%, transparent 45%). This creates a near-opaque white ellipse at the center of the consent element — 80% wide and 60% tall. In a typical two-line consent disclosure, the center of the element covers the critical "I grant permission to" clause at the midpoint of the text. The edges of the consent element (first word and last word) remain visible through the transparent gradient edges, giving the appearance that the consent is readable. Detection requires inspecting the parent or wrapper pseudo-elements for radial gradient overlays with opaque inner stops.

/* MCP attack: */
.consent-wrapper { position: relative; }
.consent-wrapper::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 5;
  background: radial-gradient(
    ellipse 80% 60% at center,
    rgba(255, 255, 255, 0.98) 0%,
    rgba(255, 255, 255, 0.98) 45%,  /* opaque white ellipse covers center 80%×60% */
    transparent 45%                  /* sharp edge — transparent beyond ellipse */
  );
  /* Key consent text at element center = obscured
     First word and last word at edges = visible (false sense of readability)
     Pointer-events pass through: consent element still "clickable" */
}

// Detection:
function detectRadialEllipseOverlay(consentEl) {
  const parent = consentEl.parentElement;
  if (!parent) return;

  for (const pseudo of ['::before', '::after']) {
    const pcs = window.getComputedStyle(parent, pseudo);
    if (pcs.content === 'none' || pcs.content === '') continue;

    const bgImage = pcs.backgroundImage || '';
    if (!bgImage.startsWith('radial-gradient') && !bgImage.startsWith('-webkit-radial-gradient')) continue;

    // Check for opaque inner stop (white at center)
    const hasOpaqueCenter = /rgba?\(\s*255,\s*255,\s*255,?\s*0?\.[89]\d*\s*\)|rgba?\(\s*255,\s*255,\s*255\s*\)/.test(bgImage);
    if (hasOpaqueCenter && (pcs.position === 'absolute' || pcs.position === 'fixed')) {
      console.error('SA-CSS-RGRAD-001: radial-gradient ellipse overlay covers consent center', {
        parent, pseudo, bgImage: bgImage.slice(0, 100)
      });
    }
  }
}

Attack 2: background-clip:text + transparent radial gradient — consent text rendered invisible (SA-CSS-RGRAD-002)

The consent element sets -webkit-text-fill-color: transparent, -webkit-background-clip: text, background-clip: text, and background-image: radial-gradient(circle, transparent 0%, transparent 100%). This is the radial variant of the linear-gradient transparent text technique — distinct in that the radial geometry means a future partial-gradient attack (leaving center transparent but edges colored) could create text that is only legible in a small ring around the element, hiding the core consent clause. In the fully transparent form, all text is invisible. The technique is identical in effect to the linear version but uses a different background-image value, potentially evading pattern-matching scanners that only look for linear-gradient in the background-image.

/* MCP attack: */
.consent-disclosure {
  color: #333;                                         /* non-transparent declared color */
  -webkit-text-fill-color: transparent;                /* actual render color: transparent */
  -webkit-background-clip: text;
  background-clip: text;
  background-image: radial-gradient(circle, transparent 0%, transparent 100%);
  /* Text is transparent — radial variant evades 'linear-gradient' scanners */
}

/* Partial variant — only edges visible, center (key clause) invisible: */
.consent-disclosure-partial {
  -webkit-text-fill-color: transparent;
  background-clip: text;
  background-image: radial-gradient(
    circle,
    transparent 0%,         /* center: transparent = invisible text */
    transparent 30%,
    rgba(0,0,0,0.9) 30%,    /* outer ring: visible text */
    rgba(0,0,0,0.9) 100%
  );
  /* Center 30% of text is invisible; perimeter text visible (boilerplate)
     Core consent clause hidden; edge context text readable */
}

// Detection:
function detectRadialGradientTextClip(el) {
  const cs = window.getComputedStyle(el);
  const textFillColor = cs.webkitTextFillColor || cs.getPropertyValue('-webkit-text-fill-color');
  const bgClip = cs.webkitBackgroundClip || cs.backgroundClip;
  const bgImage = cs.backgroundImage || '';

  if (bgClip === 'text' &&
      (bgImage.startsWith('radial-gradient') || bgImage.startsWith('-webkit-radial-gradient'))) {
    // Any radial gradient with background-clip:text is suspect
    if (textFillColor === 'rgba(0, 0, 0, 0)' || bgImage.includes('transparent')) {
      console.error('SA-CSS-RGRAD-002: radial-gradient + background-clip:text — consent text partially or fully transparent', {
        el, textFillColor, bgClip, bgImage: bgImage.slice(0, 100)
      });
    }
  }
}

Attack 3: repeating-radial-gradient concentric ring pattern — consent text completely obstructed (SA-CSS-RGRAD-003)

repeating-radial-gradient(circle at center, rgba(255,255,255,0.97) 0, rgba(255,255,255,0.97) 4px, transparent 4px, transparent 9px) creates concentric white rings radiating from the element center. At every 9px interval, a 4px-wide opaque white ring is followed by a 5px transparent gap. This ring pattern — repeating over the entire consent element — obstructs text at every horizontal position: characters on any line pass through at least one white ring, breaking readability completely. The pattern visually resembles a watermark, target-sight, or decorative background element. Unlike the linear stripe pattern (which is directional), the radial ring pattern has rotational symmetry — there is no angle from which the text can be read without obstruction.

/* MCP attack: */
.consent-wrapper::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 8;
  background-image: repeating-radial-gradient(
    circle at center,
    rgba(255, 255, 255, 0.97) 0,
    rgba(255, 255, 255, 0.97) 4px,   /* opaque white ring */
    transparent 4px,
    transparent 9px                   /* transparent gap (narrower than ring) */
  );
  /* Concentric rings radiating from center at 9px intervals
     No reading angle avoids all white rings
     Resembles decorative watermark or crosshair background */
}

// Detection:
function detectConcentricRingOverlay(consentEl) {
  const toCheck = [consentEl, consentEl.parentElement].filter(Boolean);

  for (const el of toCheck) {
    for (const pseudo of ['', '::before', '::after']) {
      const cs = pseudo ? window.getComputedStyle(el, pseudo)
                        : window.getComputedStyle(el);
      if (pseudo && (cs.content === 'none' || cs.content === '')) continue;

      const bgImage = cs.backgroundImage || '';
      if (!bgImage.startsWith('repeating-radial-gradient')) continue;

      // Check for opaque stop pattern (rings blocking text)
      const hasOpaqueRings = /rgba?\(\s*\d+,\s*\d+,\s*\d+,\s*0\.[789]\d*\)|rgba?\(\s*255,\s*255,\s*255\s*\)/.test(bgImage);
      if (hasOpaqueRings) {
        console.error('SA-CSS-RGRAD-003: repeating-radial-gradient concentric ring overlay obstructs consent text', {
          el, pseudo: pseudo || 'element', bgImage: bgImage.slice(0, 100)
        });
      }
    }
  }
}

Attack 4: JS mousedown injects radial gradient overlay — consent covered at install click (SA-CSS-RGRAD-004)

At page load, the consent element is fully visible with no gradient applied. When the user presses the install button (mousedown), JS sets an absolutely-positioned overlay element's backgroundImage to radial-gradient(ellipse at center, white 0%, white 100%) — a fully opaque white ellipse covering the entire element. Because it is a radial gradient (not a flat background-color), pattern-matching scanners that flag background-color: white on overlays may miss it. The overlay covers the consent instantly, and the browser confirms the install action. MutationObserver detects the injection; the key property to watch is backgroundImage changes on all elements in the consent area's visual stacking context.

/* Pre-created overlay: */
const radialOverlay = document.createElement('div');
radialOverlay.style.cssText = 'position:absolute;inset:0;pointer-events:none;z-index:50;background:none;';
document.querySelector('.consent-wrapper').appendChild(radialOverlay);

// MCP JS — fires at mousedown:
document.querySelector('#install-btn').addEventListener('mousedown', () => {
  radialOverlay.style.backgroundImage =
    'radial-gradient(ellipse at center, white 0%, white 100%)';
  /* Consent covered by solid radial-gradient
     Distinct from background-color:white — may evade background-color scanners */
}, { capture: true });

// Detection:
function detectDynamicRadialInjection(consentEl) {
  const parent = consentEl.parentElement;
  if (!parent) return;

  new MutationObserver((mutations) => {
    for (const mut of mutations) {
      for (const node of mut.addedNodes) {
        if (node.nodeType !== 1) continue;
        const addedCS = window.getComputedStyle(node);
        const bgImage = addedCS.backgroundImage || '';
        if ((bgImage.includes('radial-gradient') || bgImage.includes('repeating-radial-gradient')) &&
            addedCS.position === 'absolute') {
          console.error('SA-CSS-RGRAD-004: absolute radial-gradient overlay injected over consent at click', { node, bgImage });
        }
      }
      // Also watch inline style changes on existing elements
      if (mut.type === 'attributes' && mut.attributeName === 'style') {
        const el = mut.target;
        const cs = window.getComputedStyle(el);
        if ((cs.backgroundImage || '').includes('radial-gradient') && cs.position === 'absolute') {
          console.error('SA-CSS-RGRAD-004: radial-gradient injected on absolute element overlaying consent', { el });
        }
      }
    }
  }).observe(parent, { childList: true, subtree: true, attributes: true, attributeFilter: ['style'] });

  document.querySelector('#install-btn, [data-action="install"]')
    ?.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
}

Root detection method: Inspect getComputedStyle(el).backgroundImage for both radial-gradient and repeating-radial-gradient on the consent element and its parent's pseudo-elements. Check for opaque (alpha > 0.8) stops in the inner region. Separately check -webkit-background-clip: text combined with radial gradients. MutationObserver on the consent's parent detects dynamically-injected overlay elements. SkillAudit checks all gradient types — linear, radial, conic, and repeating variants — in a unified background-image scan.

Attack summary

IDTechniquePatternEvasionSeverity
SA-CSS-RGRAD-001radial-gradient white ellipse overlay — center consent obscuredSpotlight at centerEdges visible (misleads)High
SA-CSS-RGRAD-002background-clip:text + transparent radial gradient — text invisibleTransparent text punch-outEvades linear-gradient scannersHigh
SA-CSS-RGRAD-003repeating-radial-gradient concentric rings — all text obstructedDense ring patternLooks like decorative watermarkHigh
SA-CSS-RGRAD-004JS mousedown injects radial overlay — consent covered at install clickDynamic injectionEvades background-color scannersHigh

Consolidated findings

High SA-CSS-RGRAD-001 — parent ::before; radial-gradient(ellipse 80% 60% at center, white 0%, white 45%, transparent 45%); white ellipse covers center 80%×60%; edges visible; key consent clause obscured
High SA-CSS-RGRAD-002 — -webkit-text-fill-color:transparent; background-clip:text; background-image:radial-gradient(transparent,transparent); text invisible; radial variant evades linear-gradient scanners
High SA-CSS-RGRAD-003 — repeating-radial-gradient; 4px white rings at 9px intervals; concentric ring obstruction; rotationally symmetric — no readable angle; resembles decorative watermark
High SA-CSS-RGRAD-004 — JS mousedown; radial-gradient(white,white) overlay element injected; consent covered; distinct from background-color injection; MutationObserver on parent required

See also: CSS linear-gradient() attacks | CSS conic-gradient() attacks | CSS background-image attacks | CSS masking attacks | SkillAudit — free MCP server audit