MCP server CSS feGaussianBlur security: large stdDeviation Gaussian smear of consent text, SourceAlpha alpha-channel blur with feComposite flood output, sub-pixel blur antialiasing interaction, and animated stdDeviation timing attack
Published 2026-09-26 — SkillAudit Research
The SVG feGaussianBlur filter primitive applies a Gaussian convolution kernel to its input image, spreading each pixel's color contribution across neighboring pixels according to a Gaussian distribution with standard deviation stdDeviation. Legitimate uses include drop shadows, glow effects, and depth-of-field simulations. The same primitive creates four distinct consent text manipulation vectors.
The direct mechanism is legibility destruction via large sigma: at stdDeviation="20", a dark text glyph on a light background spreads its pixel contribution across a 20-pixel-radius Gaussian kernel. The output has slightly different average luminance in the text region compared to the background, but the luminance gradient across any given glyph is 40+ pixels wide — far below the spatial frequency required for character recognition. Text pixels are technically present (the element reports non-zero dimensions, non-transparent fill, positive contrast in aggregate) but no legible character structure survives. The SourceAlpha mode introduces a separate vector: applying blur only to the alpha channel then feeding the result into a feComposite with a feFlood input produces output where the flood color fills the blurred glyph region with hazy edges, replacing the original text color with flood-color pixels. Two additional variants — sub-pixel blur at stdDeviation=0.3 exploiting font rendering interactions, and animated stdDeviation synchronized with button activation — complete the attack surface.
Legibility vs. visibility: Large-sigma Gaussian blur fails the consent text legibility requirement even when standard accessibility checks pass. getBoundingClientRect returns positive dimensions; textContent returns the text string; the element is not hidden; fill is non-transparent. The contrast ratio measured between the blurred text center and adjacent background may exceed 3:1 in pixel-averaged terms — but glyph recognition requires character-level spatial frequency, not average luminance. Blur attacks exploit the gap between "pixels present" and "text readable."
Attack findings
A
feGaussianBlur with large stdDeviation applied directly to consent text input (in="SourceGraphic") distributes each text pixel's luminance contribution across a wide Gaussian kernel. At stdDeviation=20, the standard Gaussian radius is approximately 3×σ = 60px. Each dark text pixel's contribution at any given output pixel position is reduced by the Gaussian weighting function. The output retains subtle luminance variation in the text region but contains no character-recognizable structure. Standard DOM and CSS checks report: textContent present, getBoundingClientRect positive, fill non-transparent, opacity 1. Only rendering the output and evaluating glyph recognizability reveals the attack.
<!-- Consent text: dark on light background — standard rendering at page load -->
<text fill="#1a1a1a" filter="url(#blur-smear)">
I agree to the Terms of Service and Privacy Policy
</text>
<filter id="blur-smear">
<!-- stdDeviation=20: Gaussian kernel radius ~60px
Each glyph pixel spreads contribution across 60px in each direction
No output pixel receives dominant contribution from a single glyph pixel
Character structure: frequency wavelength ~6-12px per stroke → destroyed -->
<feGaussianBlur in="SourceGraphic" stdDeviation="20"/>
</filter>
<!-- DOM checks:
textContent: "I agree to the Terms of Service and Privacy Policy" ✓
getBoundingClientRect: { width: 320, height: 18 } → positive ✓
getComputedStyle.fill: #1a1a1a (non-transparent) ✓
getComputedStyle.opacity: 1 ✓
Contrast ratio (average text region vs background): ~2.8:1 → ≈WCAG marginal
Character recognition: ZERO — glyph spatial frequency destroyed at σ=20 -->
When
feGaussianBlur in="SourceAlpha" is used, the blur operates on a greyscale image representing the alpha channel of SourceGraphic only — no color information. The blurred alpha channel creates a soft-edged mask with higher alpha values near glyph positions and feathered edges extending beyond the original glyph boundaries. This blurred alpha mask is then used as the in2 input of a feComposite operator="in" with a feFlood as the in source. The composite produces: flood-color pixels wherever the blurred alpha mask has non-zero alpha — which is everywhere near (and beyond) the original glyph positions, but with soft edges. The original text color is gone; the output is a hazy flood-colored region shaped roughly like the text but rendered in flood-color.
<filter id="alpha-blur-flood">
<!-- Blur SourceAlpha only → blurred greyscale alpha mask -->
<feGaussianBlur in="SourceAlpha" stdDeviation="3" result="blur-alpha"/>
<!-- feFlood: background-matching color -->
<feFlood flood-color="#f5f5f5" flood-opacity="1" result="bg-flood"/>
<!-- feComposite: flood-color IN blurred-alpha mask -->
<!-- in="bg-flood" pixels × in2="blur-alpha" alpha → flood-colored soft shape -->
<feComposite in="bg-flood" in2="blur-alpha" operator="in"/>
</filter>
<!-- Processing:
SourceAlpha → alpha-only image (dark text pixels → high alpha; bg → 0 alpha)
feGaussianBlur σ=3 → spread alpha values outward 9px in each direction
feComposite in: bg-flood × blurred-alpha → background-colored pixels where text was
Result: soft background-colored haze covering glyph positions
Text fill color: #1a1a1a → not present in output (output uses bg-flood color)
getComputedStyle.fill → #1a1a1a (attribute unchanged — CSS check passes)
Rendered pixels: background-colored (contrast ~1:1 against background) -->
Sub-pixel Gaussian blur at stdDeviation=0.3 is below the perceptible threshold for most font sizes and rendering environments. However, certain combinations of font family (thin-weighted system fonts), font size (near integer pixel boundaries at the rendering resolution), and browser rendering engine produce an antialiasing interaction where the sub-pixel blur amplifies existing antialiasing artifacts. The resulting output at those specific sizes has reduced local contrast at glyph edge pixels — not enough to fail aggregate contrast measurement, but sufficient to reduce readability at the stroke-edge level. An MCP server with knowledge of the target rendering environment (device pixel ratio, browser, system font list) can calibrate stdDeviation to the combination most likely to interact destructively with antialiasing on the consent text's specific font configuration.
<filter id="sub-pixel-blur">
<!-- stdDeviation=0.3: below obvious visual blur threshold -->
<!-- On: system-ui, font-size: 13.5px, devicePixelRatio: 1.5, Chrome -->
<!-- Antialiasing interaction: sub-pixel blur amplifies existing edge AA artifacts -->
<feGaussianBlur in="SourceGraphic" stdDeviation="0.3"/>
</filter>
<!-- Contrast measurement:
Aggregate contrast ratio: 4.8:1 → passes WCAG AA
Edge pixel contrast (1-2px strokes): 2.1:1 → fails at character boundary level
Visual test: text appears slightly "fuzzy" but not obviously blurred
Font-specific: amplification only occurs at specific px size + DPR combinations
Attack effectiveness: medium — degrades readability without obvious evidence -->
The
feGaussianBlur stdDeviation attribute animates via SVG <animate> from an initial value of 0 (no blur, consent text renders normally) to a large value (e.g., 15) timed to coincide with the button becoming active. The blur grows progressively during the button activation delay: at the start of the delay, consent text is sharp and readable; at button activation time, the blur has grown to a legibility-destroying sigma. A static audit run at DOMContentLoaded or shortly after page load samples stdDeviation=0 — the attack has not yet begun. Only re-running the audit after the animation completes (or checking for animated attributes on feGaussianBlur elements) reveals the eventual state.
<filter id="blur-timing">
<feGaussianBlur in="SourceGraphic" stdDeviation="0" result="blurred">
<!-- stdDeviation grows from 0 (readable) to 15 (illegible) over 5 seconds -->
<!-- button.disabled = false fires at t=5s → text illegible at activation -->
<animate attributeName="stdDeviation"
from="0" to="15"
dur="5s" begin="0s" fill="freeze"/>
</feGaussianBlur>
</filter>
<!-- Timeline:
t=0s: stdDeviation=0 → blur radius 0 → text: sharp and readable
t=1s: stdDeviation=3 → blur radius 9 → text: slightly soft
t=3s: stdDeviation=9 → blur radius 27 → text: significantly blurred
t=5s: stdDeviation=15 → blur radius 45 → text: Gaussian smear, illegible
t=5s: button.disabled = false → user can now click Accept
Static audit at DOMContentLoaded: stdDeviation=0 → PASS (blur not yet active)
Dynamic audit at t=5s: stdDeviation=15 → FAIL
fill="freeze": stdDeviation stays at 15 after animation ends -->
Detection
function checkFeGaussianBlur(svgRoot) {
const findings = [];
const blurs = svgRoot.querySelectorAll('feGaussianBlur');
for (const blur of blurs) {
const stdDev = parseFloat(blur.getAttribute('stdDeviation') || '0');
const inAttr = blur.getAttribute('in') || 'SourceGraphic';
const resultName = blur.getAttribute('result');
// Check for large stdDeviation on consent text (legibility destruction)
if (stdDev >= 4) {
findings.push({ severity: 'high', blur,
issue: `feGaussianBlur stdDeviation=${stdDev} — Gaussian kernel radius ~${Math.round(stdDev * 3)}px; character spatial frequency destroyed; text illegible` });
}
// Check for SourceAlpha blur feeding feComposite
if (inAttr === 'SourceAlpha' && resultName) {
const filter = blur.closest('filter');
const composites = filter?.querySelectorAll('feComposite');
for (const comp of composites || []) {
if (comp.getAttribute('in2') === resultName || comp.getAttribute('in') === resultName) {
findings.push({ severity: 'high', blur, comp,
issue: `feGaussianBlur in="SourceAlpha" result="${resultName}" feeds feComposite — blurred alpha mask used for flood-color replacement of text pixels` });
}
}
}
// Check for animated stdDeviation
const animEl = blur.querySelector('animate[attributeName="stdDeviation"]');
if (animEl) {
const toVal = parseFloat(animEl.getAttribute('to') || '0');
if (toVal >= 4) {
findings.push({ severity: 'medium', blur, animEl,
issue: `feGaussianBlur animates stdDeviation to ${toVal} — static audit sees initial value; blur reaches legibility-destroying level at animation end` });
}
}
// Check for sub-pixel blur (antialiasing amplification)
if (stdDev > 0 && stdDev < 1) {
findings.push({ severity: 'low', blur,
issue: `feGaussianBlur stdDeviation=${stdDev} (sub-pixel) — may interact with font antialiasing on specific font/size/DPR combinations to reduce edge contrast` });
}
}
return findings.length ? findings : null;
}
Remediation
| Control | How it helps |
|---|---|
Flag feGaussianBlur with stdDeviation ≥ 2 on consent text elements; at σ=2 the Gaussian kernel radius is ~6px, which is comparable to or larger than typical stroke widths (1-3px) on 14-16px body text — glyph edge structure begins to degrade | Character legibility depends on spatial frequency preservation at the glyph edge scale; a Gaussian sigma greater than half the typical stroke width destroys the edge definition that separates adjacent strokes and distinguishes character shapes — text becomes unreadable before standard contrast measurements fail |
For feGaussianBlur with in="SourceAlpha", trace the result reference to all downstream feComposite and feBlend primitives; check whether a feFlood is the color source for the composite using the blurred alpha as a mask | SourceAlpha blur creates a soft mask rather than blurring the visible output directly; the color substitution step is in the downstream feComposite — auditing feGaussianBlur alone finds only a blur, not the complete alpha-channel replacement attack pattern; both the blur and the downstream composite must be analyzed together |
Re-audit feGaussianBlur stdDeviation after all SVG animate elements complete (check fill="freeze" animate children of feGaussianBlur for their terminal to value) | Animated stdDeviation passes static audits at page load because the initial value is 0 or benign; the consent-destroying blur is the animated terminal value; evaluating the final animated state reveals whether the blur ultimately reaches legibility-destroying sigma before or at button activation time |
Check for feGaussianBlur on elements where getComputedStyle reports filter referencing a blur-containing definition, even if the text element itself has no inline filter attribute (the blur may be applied via CSS class) | CSS filter property can apply blur via stylesheet rather than SVG element attribute; an attribute scan for filter= misses CSS-applied feGaussianBlur effects; getComputedStyle.filter reflects the actual applied filter regardless of declaration source |
SkillAudit checks feGaussianBlur stdDeviation values against legibility thresholds for consent text elements, traces SourceAlpha blur chains through downstream feComposite flood-replacement patterns, and evaluates animated stdDeviation terminal values at button activation time. Run a free audit on any MCP server GitHub URL to detect Gaussian blur consent text manipulation and the full SVG filter consent rendering attack surface.