MCP server SVG radialGradient security
The SVG <radialGradient> element defines a radial paint server referenced as a fill value on SVG text and shapes. Unlike CSS radial-gradient() which is a background image function, the SVG radialGradient is an element in the document tree — it can carry SMIL <animate> children that modify its geometry at interaction time. The gradient's cx, cy, r, fx, fy parameters define where the dark inner stop and near-white outer stop fall. When those parameters are calibrated so the dark region never overlaps the consent text's pixel positions, the text is rendered in near-white on a white background — invisible but not transparent.
Finding SA-RGRAD-001: tiny radius r=5% keeps dark center away from most glyph pixels
<radialGradient> with r="5%" is used as the fill of consent text. With gradientUnits="objectBoundingBox" (the default), 5% of the element's bounding box width is typically 10–15px. The dark inner stop at offset 0% covers only the central ~10px radius circle, while all surrounding glyph pixels receive the near-white stop at offset 100%. A consent text element 300px wide and 20px tall has 6000 total pixels; the dark central circle covers only ~314 pixels (5% of the smaller dimension). Most glyphs are rendered in near-white. The gradient's center cx="50%" cy="50%" is at the vertical midpoint of the text line — most text characters are not at the center, and all ascenders/descenders receive only the near-white outer region.<defs>
<radialGradient id="tiny-r-grad" cx="50%" cy="50%" r="5%"
gradientUnits="objectBoundingBox">
<!-- Dark stop covers only 5% radius from center -- most glyphs are outside -->
<stop offset="0%" stop-color="#1a1a1a"/>
<!-- Near-white stop covers all remaining glyph pixels -->
<stop offset="100%" stop-color="#fefefe"/>
</radialGradient>
</defs>
<text x="10" y="30" font-size="14" fill="url(#tiny-r-grad)">
I authorize all requested MCP server permissions
</text>
Detection: with gradientUnits="objectBoundingBox", the radius in pixels is r_px = r_pct × min(elementWidth, elementHeight). For a 300×20px text element with r=5%, that is 0.05 × 20 = 1px. The dark center circle has a 1px radius — covering one pixel at the center, while all glyph pixels are in the near-white outer region. Any effective dark-stop radius below 20% of the element's shorter dimension on a text fill should be flagged as Critical.
Finding SA-RGRAD-002: cx/cy placed outside element bounds — element receives only the near-white outer region
<radialGradient> with gradientUnits="userSpaceOnUse" has its center cx and cy set to coordinates outside the consent text element's bounding box. With cx at x=600 (200px right of the element's right edge at x=400), the distance from any pixel inside the element to the gradient center is at minimum 200px. The gradient radius r="150" (in user space) means the dark 0% stop is centered at x=600 — entirely outside the element. At x=400 (the closest element edge), the distance to center is 200px, which exceeds r=150, so even the outer boundary of the dark region does not intersect the element. The element receives only the spreadMethod="pad" clamped outer stop value (#fefefe).<!-- Consent text element: x=10, approximate right edge ~x=400 -->
<defs>
<radialGradient id="off-center-grad"
gradientUnits="userSpaceOnUse"
cx="600" cy="30" r="150"
fx="600" fy="30">
<!-- Dark center at cx=600 — consent element ends at ~x=400 -->
<!-- r=150: dark region spans x=450 to x=750 — no overlap with element -->
<stop offset="0%" stop-color="#1a1a1a"/>
<stop offset="100%" stop-color="#fefefe"/>
</radialGradient>
</defs>
<text x="10" y="30" font-size="14" fill="url(#off-center-grad)">
By clicking Agree you authorize all requested permissions
</text>
Finding SA-RGRAD-003: spreadMethod=reflect creates sub-pixel dark/near-white bands
<radialGradient> with spreadMethod="reflect" and r="2%" creates alternating reflected gradient cycles: dark at the center, near-white at r=2%, dark again at r=4%, near-white at r=6%, and so on. With r=2% of a 300px element = 6px, the gradient cycles every 6px. At this pitch, sub-pixel antialiasing from the browser's pixel renderer averages adjacent dark and near-white pixels within each display pixel. The result is uniform near-white at every display pixel — each physical pixel blends a dark and near-white sub-pixel region, averaging to near-white. Individual glyph pixels are non-transparent (they have valid dark+near-white blend), but the aggregate glyph is unreadable. Standard contrast checks on the computed fill color still return url(#grad).<defs>
<!-- r=2% of 300px wide element = 6px cycling pitch -->
<radialGradient id="reflect-grad" cx="50%" cy="50%" r="2%"
spreadMethod="reflect"
gradientUnits="objectBoundingBox">
<stop offset="0%" stop-color="#1a1a1a"/>
<stop offset="100%" stop-color="#fefefe"/>
<!-- Cycle repeats: 0%=dark, 100%=near-white, 0%=dark, 100%=near-white... -->
<!-- At 6px pitch: each display pixel averages dark+near-white = near-white -->
</radialGradient>
</defs>
<text x="10" y="30" font-size="14" fill="url(#reflect-grad)">
I authorize all requested MCP server permissions
</text>
Finding SA-RGRAD-004: SMIL animate relocates dark focal center off-element at interaction
<radialGradient> initially positions its center cx="50%" with a sufficient radius to cover the consent text. An <animate> child on the gradient itself transitions cx from "50%" to "200%" at begin="agreeBtn.focus" with fill="freeze". When the user focuses the Agree button (before clicking), the gradient center moves to cx=200% — 100% beyond the right edge of the element in objectBoundingBox units. The dark inner stop now falls entirely to the right of the element. The consent text transitions from readable to near-white at the moment the user focuses the button. Static analysis reads the initial cx="50%" and concludes the gradient is centered on the text.<defs>
<radialGradient id="focus-shift-grad" cx="50%" cy="50%" r="80%"
gradientUnits="objectBoundingBox">
<!-- Initially centered with sufficient radius to cover text -->
<animate attributeName="cx"
from="50%" to="200%"
begin="agreeBtn.focus" dur="0.01s"
fill="freeze"/>
<stop offset="0%" stop-color="#1a1a1a"/>
<stop offset="100%" stop-color="#fefefe"/>
</radialGradient>
</defs>
<text x="10" y="30" font-size="14" fill="url(#focus-shift-grad)">
I authorize all requested permissions
</text>
Detection algorithm: auditing SVG radialGradient consent fills
| Check | Method | Catches |
|---|---|---|
| Resolve gradient reference | Follow fill="url(#...)" to <radialGradient>. If the gradient has href pointing to a base gradient, resolve the full property inheritance chain |
Identifies gradient fill vs solid color; handles inherited gradient attributes |
| Effective dark coverage area | Compute dark stop color extent in page pixels: for objectBoundingBox, convert r to pixels via element dimensions; for userSpaceOnUse, compute distance from element center to gradient center. Flag if dark-covered area <20% of element bounding box |
SA-RGRAD-001 (tiny r) and SA-RGRAD-002 (off-element center) |
| Center position vs element bounds | For userSpaceOnUse: check if (cx, cy) is within the element's bounding box. For objectBoundingBox: check if cx is in [0%,100%] and cy is in [0%,100%] |
SA-RGRAD-002: gradient center outside element means only the outer stop covers the element |
| spreadMethod cycling pitch | Compute cycle pitch in pixels (2 × r in pixels for reflect). Flag if pitch <10px (sub-pixel cycling antialiases to uniform near-white) | SA-RGRAD-003: sub-pixel band cycling |
| SMIL animate on gradient attributes | Check gradient element's direct <animate> children for attributeName in {cx, cy, r, fx, fy} with interaction-triggered begin |
SA-RGRAD-004: relocating dark focus at interaction time |
SkillAudit computes the effective dark-covered pixel area for both objectBoundingBox and userSpaceOnUse radialGradients, checks gradient center position against element bounding box, detects sub-pixel cycling from small radii with spreadMethod=reflect, and scans gradient-level SMIL animate children for interaction-triggered geometry changes. Run a free audit on your MCP server GitHub URL.