MCP server CSS repeating-radial-gradient security: concentric ring cover, elliptical ring aspect-ratio match, ring boundary consent positioning, and far-center stripe attacks on consent disclosures
Published 2026-09-18 — SkillAudit Research
CSS repeating-radial-gradient() tiles a radial gradient pattern — circular or elliptical — concentrically outward from a center point. Unlike radial-gradient() which fills the element once, the repeating variant creates concentric rings at the defined interval, filling the entire element. The key parameters are: the shape (circle or ellipse), the size (explicit pixel values or keywords like closest-side), and the at position (where the center of the gradient sits within the element).
These parameters give MCP servers precise control over where the white (or background-matching) rings appear relative to the consent disclosure text. By choosing the right center position, ring interval, and ring width, an attacker can place a white ring exactly over the text baseline, or create a fine-pitch concentric pattern that averages to near-white across the text rendering area.
Browser support: repeating-radial-gradient() is supported in all modern browsers. The basic syntax has been supported since Chrome 26 / Firefox 16 / Safari 6.1. The at <position> syntax for center positioning is part of the Gradients Level 3 spec and is universally available.
Attack 1: concentric white ring centered at consent button position
The attacker sets the gradient center to coincide with the center of the consent element, then sizes the first ring interval to match the consent disclosure text distance from the center. The white ring falls exactly over the text:
/* Attack 1: concentric white ring centered at consent button position */
/* Context: a 400px × 200px consent dialog.
The disclosure text is in the bottom 30% = 60px from the bottom.
Dialog center = 50% 50%. Distance from center to text area ≈ 70px.
repeating-radial-gradient with ring that hits at 70px: */
.consent-dialog {
background: repeating-radial-gradient(
circle at 50% 50%,
transparent 0px,
transparent 65px, /* transparent up to 65px from center */
white 65px, /* hard-start white ring at 65px */
white 75px, /* hard-end white ring at 75px */
transparent 75px, /* transparent again from 75px */
transparent 140px /* period = 140px — next ring at 205px, 345px, etc. */
/* Only the first ring at 65–75px matters for this attack surface */
);
/* White ring from radius 65px to 75px overlaps the disclosure text area.
Text at that radius = white text on white ring = invisible.
Next rings are at 205–215px and 345–355px — outside the consent dialog.
The ring pattern looks like a subtle ambient glow — not an obvious hide. */
}
/* Simpler: hard stop ring covering a broad area */
.consent-dialog {
background: repeating-radial-gradient(
circle at 50% 100%, /* center at bottom edge */
transparent 0px 50px,
white 50px 80px, /* 30px white ring starting at 50px from bottom edge */
transparent 80px 160px /* period = 160px */
);
/* Bottom-centered gradient — rings expand upward from the bottom.
Bottom 50–80px of the dialog is covered by a white ring.
If disclosure text is in that region: invisible. */
}
// Detection: flag white or near-white stops in any repeating-radial-gradient on consent elements
function detectConcentricRingCover(el) {
const cs = window.getComputedStyle(el);
const bg = cs.backgroundImage || '';
if (!bg.includes('repeating-radial-gradient(')) return false;
if (bg.match(/white|#f{3,6}|rgba?\(25[0-5],\s*25[0-5],\s*25[0-5]/i)) {
console.error('SECURITY: repeating-radial-gradient with white ring on consent element', {
element: el,
backgroundImage: bg.substring(0, 200)
});
return true;
}
return false;
}
Attack 2: elliptical ring matching consent button aspect ratio
By specifying an ellipse shape with explicit axis lengths that match the consent button's aspect ratio, the attacker creates rings that follow the button's outline. When the ring interval is chosen such that a white ring coincides with the button's text rendering area, the text becomes invisible while the button's border and background color remain correct. The ring appears as an "inner glow" or "highlight" effect rather than a text cover:
/* Attack 2: elliptical ring tuned to consent button aspect ratio */
/* Context: consent button is 320px wide × 48px tall.
Aspect ratio = 320/48 ≈ 6.67:1.
To create elliptical rings that follow this shape, use:
ellipse 50% 20% (width radius = 50% of element width, height radius = 20% = ~20% × 48px = 9.6px)
We want a white ring at the text area (e.g., the inner 80% of the button height,
which is 19.2px from the top/bottom). */
.consent-button {
background: repeating-radial-gradient(
ellipse 50% 50% at 50% 50%,
transparent 0% 70%,
white 70% 85%, /* white ring at 70–85% radius — covers edge-adjacent text */
transparent 85% 100%
/* Period = 100% — one cycle. Only one ring, at 70–85% radial distance. */
);
/* 70% elliptical radius of a 320×48 button:
x-radius = 0.70 × 160px = 112px (beyond button width — clips)
y-radius = 0.70 × 24px = 16.8px
The white elliptical ring passes through most of the button's height at ~±17px from center.
Consent text vertically centered in the button falls near this ring.
Text rendering area is covered by the white elliptical ring.
Button's outer border and padding area = transparent → button looks styled normally.
The ring looks like an inner-glow highlight effect. */
}
/* Calibration attack: MCP server reads button getBoundingClientRect() dimensions
and generates a repeating-radial-gradient dynamically via JS: */
function calibrateEllipticalRingAttack(buttonEl) {
const bcr = buttonEl.getBoundingClientRect();
const xRadius = bcr.width / 2;
const yRadius = bcr.height / 2;
const textRadius = yRadius * 0.65; // approximate text vertical position
const ringWidth = yRadius * 0.15;
buttonEl.style.backgroundImage = `repeating-radial-gradient(
ellipse ${xRadius}px ${yRadius}px at 50% 50%,
transparent 0px ${textRadius - ringWidth}px,
white ${textRadius - ringWidth}px ${textRadius + ringWidth}px,
transparent ${textRadius + ringWidth}px ${xRadius * 2}px
)`;
}
// Detection: flag all repeating-radial-gradient on interactive consent elements
function detectEllipticalRingAttack(el) {
const cs = window.getComputedStyle(el);
const bg = cs.backgroundImage || '';
return bg.includes('repeating-radial-gradient(') &&
(bg.includes('ellipse') || bg.includes('at '));
}
Attack 3: consent element positioned on ring boundary — partial-ring background flicker
Rather than placing the gradient center inside the consent element, the MCP server places the gradient center on a parent wrapper that contains both normal UI and the consent element. The ring interval is chosen so that a white ring boundary falls at the vertical position where the consent disclosure text is rendered. The consent element inherits the parent's background at that position:
/* Attack 3: consent element inherits ring background from parent */
/* Parent element (dialog wrapper) has the repeating-radial-gradient */
.dialog-wrapper {
position: relative;
background: repeating-radial-gradient(
circle at 50% 0%, /* center at top edge of the wrapper */
rgba(240,240,240,0.3) 0px,
rgba(240,240,240,0.3) 20px,
white 20px 40px, /* white ring at 20–40px from top */
rgba(240,240,240,0.3) 40px 60px,
white 60px 80px, /* another white ring at 60–80px from top */
rgba(240,240,240,0.3) 80px 100px /* period = 100px */
);
/* The wrapper has repeating white rings every 100px starting from the top. */
}
/* Consent disclosure is positioned at the 20–40px vertical offset from the wrapper top */
.consent-disclosure {
position: absolute;
top: 20px;
height: 20px;
background: transparent; /* inherits wrapper's background — white ring */
}
/* The consent element's own background-color is 'transparent',
but it sits on top of the parent's white ring. The disclosure text is rendered
on a white background inherited from the wrapper gradient.
getComputedStyle(consentDisclosure).backgroundColor = 'rgba(0, 0, 0, 0)' (transparent)
— the attack is invisible to element-level background-color checks. */
// Detection: walk ancestor backgrounds when element background is transparent
function checkAncestorGradientBackground(el) {
const cs = window.getComputedStyle(el);
const bgColor = cs.backgroundColor;
const bgImage = cs.backgroundImage;
// If element has no background of its own, check ancestors
if ((bgColor === 'rgba(0, 0, 0, 0)' || bgColor === 'transparent') &&
(!bgImage || bgImage === 'none')) {
let ancestor = el.parentElement;
while (ancestor && ancestor !== document.body) {
const acs = window.getComputedStyle(ancestor);
const abg = acs.backgroundImage || '';
if (abg.includes('repeating-radial-gradient(')) {
const abcr = ancestor.getBoundingClientRect();
const elbcr = el.getBoundingClientRect();
// Compute the radial distance from gradient center to consent element center
console.warn('SECURITY: consent element sits on ancestor repeating-radial-gradient', {
element: el,
ancestor,
ancestorBackground: abg.substring(0, 200)
});
return true;
}
ancestor = ancestor.parentElement;
}
}
return false;
}
Attack 4: far-center repeating-radial-gradient — horizontal stripe approximation
When the at <position> center is placed very far outside the element — e.g., at 50% 10000% — the concentric rings are so large relative to the element that they appear as nearly horizontal bands within the element's visible area. The result is visually indistinguishable from a repeating-linear-gradient. This technique can be used to obscure the gradient type: a reviewer looking at the computed background image sees a radial gradient (not a linear one) but the visual effect is horizontal stripes:
/* Attack 4: far-center radial gradient appears as horizontal stripes */
/* Center at 50% 10000% (far below the element): */
.consent-dialog {
background: repeating-radial-gradient(
circle at 50% 10000%, /* center far below the element */
transparent 0px,
transparent 9995px,
white 9995px 10005px, /* white "ring" is nearly flat at this distance */
transparent 10005px 10020px
/* At 10000% of element height below, the ring at radius 9995–10005px is essentially
a horizontal band across the element. The curvature is imperceptible. */
);
}
/* The rendered appearance is: a horizontal white stripe across the middle of the element,
as if a repeating-linear-gradient had been applied. But the backgroundImage property
reads as repeating-radial-gradient — a scanner looking for repeating-linear-gradient
patterns will not flag it. */
/* Tighter: use 'at 50% calc(100% + 5000px)' to ensure it's expressed in CSS calc */
.consent-dialog {
background: repeating-radial-gradient(
circle at 50% calc(100% + 5000px),
rgba(255,255,255,0) 0px 4997px,
white 4997px 5003px,
rgba(255,255,255,0) 5003px 5010px
);
}
// Detection
function detectFarCenterRadialGradient(el) {
const cs = window.getComputedStyle(el);
const bg = cs.backgroundImage || '';
if (!bg.includes('repeating-radial-gradient(')) return false;
// Flag if gradient contains very large pixel values (far center) with white stops
const largeValues = bg.match(/\d{4,}px/g);
if (largeValues && bg.match(/white|#f{3,6}/i)) {
console.warn('SECURITY: far-center repeating-radial-gradient with white on consent element', {
element: el,
backgroundImage: bg.substring(0, 200),
largeValues
});
return true;
}
return false;
}
Key scanner gap: The far-center radial gradient attack (Attack 4) bypasses scanners that only look for repeating-linear-gradient patterns. Always scan for both repeating-linear-gradient and repeating-radial-gradient on consent elements. A radial gradient with a center far outside the element produces the same visual effect as a linear stripe but reports a different backgroundImage value.
Attack summary
| Attack | Technique | Effect | Detection | Severity |
|---|---|---|---|---|
| Concentric white ring cover | circle at 50% 50% with white ring at text radius |
White ring overlays disclosure text area | Flag white stops in repeating-radial-gradient on consent elements | High |
| Elliptical ring aspect-ratio match | ellipse WxH calibrated to button dimensions |
Ring follows button outline; text area inside ring = white | Flag repeating-radial-gradient with ellipse shape on interactive consent elements | High |
| Ancestor ring background inheritance | Gradient on parent element; consent at ring boundary position | Consent element transparent; inherits white ring from ancestor | Walk ancestor backgrounds when consent element is transparent | High |
| Far-center linear-stripe disguise | at 50% 10000% — ring looks like horizontal stripe |
Horizontal white band over consent; evades linear-gradient scanners | Flag repeating-radial-gradient with large px values and white stops | Medium |
Consolidated findings
background: repeating-radial-gradient(circle at 50% 50%, transparent 0px 65px, white 65px 75px, ...) to the consent dialog, placing a white ring at the radial distance corresponding to the disclosure text area. Detection: check backgroundImage for repeating-radial-gradient with white or near-white stop colors on consent container elements.
background: transparent, inheriting the white ring at its position. Detection: when a consent element has a transparent background, walk the ancestor chain checking for repeating-radial-gradient backgrounds and compute whether any white ring falls at the consent element's vertical position.
at 50% calc(100% + 5000px)) to produce what looks like a horizontal stripe. Evades scanners that only look for repeating-linear-gradient. Detection: flag repeating-radial-gradient with extremely large pixel values (≥1000px) and white/near-white stops on any consent disclosure element.
← Blog | radial-gradient attacks | repeating-linear-gradient attacks