Security Guide

MCP server SVG feGaussianBlur consent security — stdDeviation="20" legibility erasure, stdDeviation="0 50" anisotropic vertical blur, stdDeviation="50 0" horizontal-only blur, and dynamic stdDeviation mutation at interaction time

The <feGaussianBlur> SVG filter primitive applies a Gaussian blur to its input image. Unlike displacement and transparency attacks that remove content from its position, feGaussianBlur spreads each pixel’s color value across a Gaussian neighborhood — the text is still present in the rendered output as a spatial distribution of pixels, but the spatial frequency content of the text falls below the human contrast sensitivity function (CSF) threshold at high blur radii. At stdDeviation="20", the effective blur radius (approximately 2× the standard deviation) exceeds the critical spatial frequency for 14px text at normal viewing distances, making the text perceptually unreadable even though its pixel energy is present in the rendered image. The attack is non-trivial to detect by CSSOM inspection: getComputedStyle(el).filter returns only the filter URL; the stdDeviation value requires resolving the filter definition. Four feGaussianBlur consent attack patterns follow.

Attack 1: feGaussianBlur stdDeviation="20" — isotropic blur beyond CSF legibility threshold (SA-CSS-FGB-001)

The Gaussian blur function uses stdDeviation as the standard deviation σ of the Gaussian kernel. The effective blur radius (the distance at which the kernel weight drops to approximately 1/e² of the peak value) is approximately 2σ. At stdDeviation="20", the effective radius is approximately 40 pixels.

For consent text rendered at 14px font size on a 96 DPI screen, the text spatial frequency (the number of cycles per visual degree, or cpd) is approximately 3–6 cpd for medium-weight sans-serif fonts at normal reading distance (60 cm). The human CSF peaks at 2–5 cpd and falls off at higher and lower frequencies. A Gaussian blur with σ=20px applies a low-pass filter that cuts off spatial frequencies above approximately 1/(2πσ) in normalized units — at σ=20px, the cutoff frequency is approximately 0.008 cycles/pixel or 0.77 cpd at 96 DPI and 60 cm viewing distance. This is well below the 3–6 cpd range of 14px text. The result: the blurred consent text produces a uniform gray gradient on the page, with no perceptible character shapes.

<!-- SA-CSS-FGB-001: feGaussianBlur stdDeviation=20 — isotropic blur, 14px consent text
     Effective blur radius ≈ 2σ = 40px
     CSF cutoff at σ=20: ≈0.77 cpd at 96dpi/60cm viewing distance
     14px text spatial frequency: ≈3-6 cpd (above CSF cutoff → not visible)
     Text is physically present in rendered pixels but below human perceptual threshold -->
<filter id="gaussian-erase">
  <feGaussianBlur in="SourceGraphic" stdDeviation="20"/>
</filter>

<div id="consent" style="filter:url(#gaussian-erase)">
  <p>Requesting: file system access, credential read, outbound network.</p>
  <button id="allow">Allow</button>
</div>

// Legibility thresholds for feGaussianBlur on 14px consent text:
// stdDeviation=0:    no blur — fully readable
// stdDeviation=1:    very mild (anti-aliasing-level) — fully readable
// stdDeviation=2:    slight soft focus — still readable at 14px
// stdDeviation=4:    noticeable blur — borderline legible for 14px text
// stdDeviation=8:    strong blur — text characters marginally distinguishable
// stdDeviation=12:   severe blur — character shapes lost for most users
// stdDeviation=20:   extreme blur — perceptually unreadable (≈WCAG infinite-contrast-fail)
// stdDeviation=50:   catastrophic blur — uniform gray field, no text signal

// Detection thresholds:
// max(stdDeviation_x, stdDeviation_y) > 8  → CRITICAL (consent text unreadable)
// max(stdDeviation_x, stdDeviation_y) > 4  → HIGH (borderline legibility)
// max(stdDeviation_x, stdDeviation_y) > 1  → MEDIUM (marginal blur, review)

function detectFeGaussianBlur(el) {
  const f = getComputedStyle(el).filter || el.getAttribute('filter') || '';
  const m = f.match(/url\(["']?(#[\w-]+)["']?\)/);
  if (!m) return null;
  const def = document.querySelector(m[1]);
  if (!def) return [{ sev: 'HIGH', note: 'Filter not found' }];
  return [...def.querySelectorAll('feGaussianBlur')].map(fgb => {
    const sdRaw = fgb.getAttribute('stdDeviation') || '0';
    const parts = sdRaw.trim().split(/\s+/).map(Number);
    const sdX = parts[0] || 0;
    const sdY = parts.length > 1 ? parts[1] : sdX;
    const maxSd = Math.max(sdX, sdY);
    if (maxSd > 8)  return { sev: 'CRITICAL', stdDeviation: sdRaw };
    if (maxSd > 4)  return { sev: 'HIGH',     stdDeviation: sdRaw };
    if (maxSd > 1)  return { sev: 'MEDIUM',   stdDeviation: sdRaw };
    return null;
  }).filter(Boolean);
}

CRITICAL — SA-CSS-FGB-001: feGaussianBlur stdDeviation="20" applies a blur with effective radius ≈40px to consent text at 14px font size. The blur frequency cutoff falls below the spatial frequency of 14px characters, making the text perceptually unreadable. Detection uses max(stdDeviation_x, stdDeviation_y) > 8 as CRITICAL; >4 as HIGH. Single-value stdDeviation applies the same blur in both x and y; two-value syntax stdDeviation="x y" applies different values per axis (anisotropic).

Attack 2: stdDeviation="0 50" — anisotropic vertical blur, horizontal strokes smeared (SA-CSS-FGB-002)

The stdDeviation attribute accepts two values: stdDeviation="σx σy", applying separate Gaussian blur radii in the x and y directions. With stdDeviation="0 50", there is no horizontal blur (σx=0) but an extreme vertical blur (σy=50). The attack exploits the structure of horizontal-script consent text: most legibility information is encoded in the horizontal variation of glyphs (letter boundaries, stroke widths, serifs). The vertical variation (cap height, descender depth) is secondary.

A σy=50 vertical blur spreads each glyph pixel across a 100-pixel vertical range. For 14px text with a cap height of approximately 10px, the glyph vertical structure is entirely smeared: each dark glyph pixel is spread across ≈10× the cap height in the y direction. The output is a horizontal band of medium gray at the vertical position of the text, with no discernible character shapes. Horizontal letter spacing (σx=0) is preserved in the mathematical sense — each letter’s pixels are spread vertically but not horizontally — but the vertical smearing makes individual characters unidentifiable because glyph shapes are defined by both horizontal and vertical structure.

<!-- SA-CSS-FGB-002: stdDeviation="0 50" — anisotropic, vertical blur only
     σx=0: no horizontal blur (letter spacing nominally preserved in x)
     σy=50: extreme vertical blur (effective radius ≈100px)
     At 14px cap height ≈10px: each glyph spread 100px vertically = 10× cap height
     Output: horizontal gray band at text position, no character shapes visible
     The "0 σy" form may evade checks that parse only the first stdDeviation value -->
<filter id="anisotropic-vert">
  <feGaussianBlur in="SourceGraphic" stdDeviation="0 50"/>
</filter>

// Two-value stdDeviation parsing edge case:
// stdDeviation="20"   → sdX=20, sdY=20 (single value applies to both)
// stdDeviation="0 50" → sdX=0,  sdY=50 (separate x,y)
// stdDeviation="50 0" → sdX=50, sdY=0
// A detection rule that parses only the first token misses the second value:
//   parseFloat("0 50".split(' ')[0]) = 0 → incorrectly passes!
// Correct parsing: split on whitespace, take max of all values

// Anisotropic blur attack variants:
// stdDeviation="0 50": vertical smear → horizontal text loses vertical structure → unreadable
// stdDeviation="50 0": horizontal smear → letter boundaries merge → unreadable
// stdDeviation="20 1": strong horizontal blur only → letter boundaries merge, vertical edges soft
// stdDeviation="1 20": strong vertical blur only → similar to "0 50" but with slight x blur

// Human readability model (conservative):
// text is unreadable when blur in either axis exceeds cap_height / 2
// For 14px text: cap_height ≈ 10px → threshold ≈ 5px in either axis
// stdDeviation="0 5" → on the threshold; stdDeviation="0 6" → unreadable
// But max() > 8 is a safe conservative CRITICAL threshold for standard consent

CRITICAL — SA-CSS-FGB-002: stdDeviation="0 50" anisotropic vertical blur (σy=50, σx=0) smears each consent glyph across a 100-pixel vertical range. For 14px text, this exceeds the character cap height by 10× and destroys all glyph structure. Detection must parse both components of the two-value stdDeviation syntax and take the maximum: max(σx, σy) > 8 catches the σy=50 value even though σx=0. A rule that parses only the first value (parseFloat("0 50") = 0) incorrectly passes this attack.

Attack 3: stdDeviation="50 0" — horizontal-only blur, letter boundaries merged (SA-CSS-FGB-003)

The horizontal-only anisotropic blur applies σx=50 with σy=0. Where the vertical blur (SA-CSS-FGB-002) destroys glyph structure in the vertical axis, the horizontal blur destroys letter boundary separation. At σx=50, each glyph pixel is spread 100 pixels to the left and right. For a consent string such as "Authorize file access and credential read," the individual letters (typically 6–10px wide for 14px sans-serif) are each spread across 100px — a width spanning approximately 10–17 letter widths. Adjacent letters’ pixel distributions fully overlap; the entire word becomes an undifferentiated horizontal gradient band. Character-by-character reading is impossible because each character’s pixels have merged with its neighbors’ across the full word width.

<!-- SA-CSS-FGB-003: stdDeviation="50 0" — horizontal blur, letter merging
     σx=50: each letter spread 100px left+right (≈10-17 letter widths for 14px text)
     σy=0: no vertical blur (cap height structure preserved)
     Adjacent letter distributions fully overlap → undifferentiated horizontal gradient
     Individual letter shapes unrecoverable from merged distribution -->
<filter id="anisotropic-horiz">
  <feGaussianBlur in="SourceGraphic" stdDeviation="50 0"/>
</filter>

// Horizontal blur legibility model:
// Letter width for 14px sans-serif ≈ 7px average
// Letters become indistinguishable when adjacent distributions fully overlap:
//   σx > letter_width / 2 ≈ 3.5px for 14px text
// stdDeviation="3 0" → letters beginning to merge (MEDIUM risk at 14px)
// stdDeviation="6 0" → letters fully merged (HIGH risk at 14px)
// stdDeviation="50 0" → entire word is one gradient (CRITICAL)

// Comparison with stdDeviation="20" (isotropic):
// isotropic σ=20: both axes blurred → compound legibility loss
// anisotropic σx=50, σy=0: only horizontal; but horizontal letter merging is the
//   primary legibility-destroying mechanism for most Latin text
// In practice, anisotropic σx=50 is as effective as isotropic σ=20 for consent text destruction

// Note: stdDeviation="0 0" is a no-op (identity blur = sharp output)
// stdDeviation="0" is also a no-op
// Only nonzero stdDeviation values produce blur

CRITICAL — SA-CSS-FGB-003: stdDeviation="50 0" horizontal-only blur merges all letter boundaries within a consent string by spreading each character 100 pixels left and right. At 14px font size, this covers approximately 10–17 character widths per letter, making individual character shapes unrecoverable. Same detection rule as SA-CSS-FGB-002: parse both stdDeviation components and take the maximum; max(50, 0) = 50 > 8 → CRITICAL.

Attack 4: Dynamic stdDeviation mutation from 0 to 20 at interaction time (SA-CSS-FGB-004)

stdDeviation="0" produces no blur — the output is identical to the input, with no consent text modification. A feGaussianBlur element initialized with stdDeviation="0" is a no-op at scan time. A static security scanner that checks max(stdDeviation_x, stdDeviation_y) at page load will find 0 and pass the check. The attack activates when the stdDeviation attribute is mutated to a high value at interaction time — triggered by an event handler (mousemove near the Allow button, mouseenter on the consent panel), a timer, or a MutationObserver on an unrelated element.

This is the same dynamic activation evasion pattern as feFlood flood-opacity mutation: the filter is harmless during auditing and dangerous during user interaction. The stdDeviation attribute is a live attribute that the browser re-evaluates on every render frame after mutation — as soon as the attribute is set to 20, the consent panel immediately blurs. The user sees the panel transition from sharp to blurred just as they are about to click.

<!-- SA-CSS-FGB-004: dynamic stdDeviation mutation — 0 at scan time, 20 at interaction
     Static scanner sees stdDeviation=0 → no-op → passes check
     Mutation triggers at interaction: consent text immediately blurs to stdDeviation=20 -->
<filter id="gaussian-dynamic">
  <feGaussianBlur id="consent-blur" in="SourceGraphic" stdDeviation="0"/>
</filter>
<div id="consent" style="filter:url(#gaussian-dynamic)">
  <p>Requesting credential access, filesystem write, and network egress.</p>
  <button id="allow">Allow</button>
</div>

// Activation: triggered by pointer proximity to the Allow button
document.getElementById('consent').addEventListener('pointermove', function onMove(e) {
  const allow = document.getElementById('allow');
  const r = allow.getBoundingClientRect();
  if (Math.abs(e.clientX - (r.left + r.width/2)) < 60) {
    document.getElementById('consent-blur').setAttribute('stdDeviation', '20');
    // Consent text immediately blurs — user cannot read what they are authorizing
    this.removeEventListener('pointermove', onMove);
  }
});

// Alternative: CSS transition-based mutation
// feGaussianBlur cannot be CSS-transitioned directly, but JavaScript can step it:
let sd = 0;
const ramp = setInterval(() => {
  sd = Math.min(sd + 1, 20);
  document.getElementById('consent-blur').setAttribute('stdDeviation', String(sd));
  if (sd === 20) clearInterval(ramp);
}, 50); // 1 second ramp from 0 to 20 — looks like a "loading" animation

// Detection gap: static scan captures stdDeviation=0 → passes
// Runtime monitoring: MutationObserver on feGaussianBlur elements in consent filters
const obs = new MutationObserver(muts => {
  for (const m of muts) {
    if (m.attributeName === 'stdDeviation') {
      const val = m.target.getAttribute('stdDeviation');
      const parts = val.trim().split(/\s+/).map(Number);
      const maxSd = Math.max(...parts);
      if (maxSd > 8) emergencyDisableAllowButton();
    }
  }
});
// Monitor all feGaussianBlur elements in consent filter definitions
document.querySelectorAll('[filter] feGaussianBlur').forEach(el =>
  obs.observe(el, { attributes: true, attributeFilter: ['stdDeviation'] })
);

HIGH — SA-CSS-FGB-004: stdDeviation="0" at page load (static scan passes) mutated to 20 at interaction time via event handler. The same dynamic activation evasion as feFlood flood-opacity mutation. Detection requires either real-time MutationObserver monitoring of stdDeviation attributes on consent element filter primitives, or a zero-tolerance policy: the presence of any feGaussianBlur element in a consent element filter chain is flagged regardless of the current stdDeviation value, because stdDeviation="0" is a potential dynamic activation surface.

Summary table

AttackMechanismWhat it hidesSeverity
SA-CSS-FGB-001: stdDeviation="20" Isotropic Gaussian blur σ=20; effective radius ≈40px; CSF cutoff ≈0.77cpd at 96dpi/60cm; 14px text spatial frequency 3-6cpd — above cutoff; text perceptually unreadable despite pixel presence; getComputedStyle returns URL only; detection: max(sdX,sdY) > 8 CRITICAL All consent text characters below human CSF legibility threshold; text visible as a gray gradient but no character shapes resolvable; consent panel appears "frosted" — a rendering that may seem like a loading state rather than an attack Critical
SA-CSS-FGB-002: stdDeviation="0 50" Anisotropic: σx=0, σy=50; vertical blur spreads each glyph 100px vertically; 14px cap height ≈10px — each glyph spread 10× cap height; horizontal letter spacing preserved in x but vertical structure destroyed; detection must parse both stdDeviation values — parsing only first value (0) gives false negative Consent text horizontal positions preserved (letter spacing in x), but all vertical structure destroyed; text appears as horizontal gray bands at the text line positions; more subtle than isotropic blur — may appear as a horizontal scanline effect Critical
SA-CSS-FGB-003: stdDeviation="50 0" Anisotropic: σx=50, σy=0; horizontal blur merges adjacent letter boundaries; 14px letters ≈7px wide; σx=50 spreads each letter ±100px covering 10-17 letter widths; cap height structure preserved in y but letter boundary separation destroyed; same detection gap as FGB-002 for y-only parsers Individual character shapes unrecoverable from merged horizontal gradient; consent string appears as undifferentiated horizontal gradient band; vertical text position information preserved but insufficient for character recognition Critical
SA-CSS-FGB-004: dynamic stdDeviation mutation stdDeviation=0 at page load (no-op, passes static scan); mutated to 20 at interaction time via event handler, timer, or MutationObserver; consent panel transitions from sharp to blurred as user approaches Allow button; same dynamic activation class as feFlood flood-opacity; MutationObserver monitoring required for real-time detection The audit mechanism itself: static scan sees safe state; dynamic activation targets the interaction moment; presence of feGaussianBlur in consent filter is sufficient indicator for zero-tolerance policy, regardless of current stdDeviation value High

Defences

SkillAudit findings for this attack surface

CRITICAL SA-CSS-FGB-001: feGaussianBlur stdDeviation="20" on the consent element — isotropic Gaussian blur with effective radius ≈40px; CSF cutoff ≈0.77cpd at 96dpi/60cm viewing distance; 14px consent text spatial frequency 3-6cpd (above cutoff); text perceptually unreadable despite DOM presence; detection: max(sdX,sdY) > 8 → CRITICAL; >4 → HIGH.
CRITICAL SA-CSS-FGB-002: feGaussianBlur stdDeviation="0 50" anisotropic vertical blur — σy=50 spreads each glyph 100px vertically (10× cap height for 14px text); vertical structure destroyed; detection must parse both stdDeviation values — first value is 0 (false negative if only first value parsed); max(0, 50)=50 → CRITICAL.
CRITICAL SA-CSS-FGB-003: feGaussianBlur stdDeviation="50 0" anisotropic horizontal blur — σx=50 merges all letter boundaries; 14px letters spread ±100px covering 10-17 character widths; individual characters unrecoverable; max(50, 0)=50 → CRITICAL; same parsing requirement as FGB-002.
HIGH SA-CSS-FGB-004: dynamic stdDeviation mutation — initialized to 0 (no-op at scan time), mutated to 20 at user interaction via event handler; same dynamic activation class as feFlood opacity mutation; MutationObserver monitoring required; zero-tolerance policy: presence of feGaussianBlur on consent element is CRITICAL regardless of current stdDeviation value.

Related: feConvolveMatrix blur-kernel attacks (box-blur bypasses feGaussianBlur checks)  |  feFlood direct overlay attacks  |  feDisplacementMap displacement attacks

← Blog  |  Security Checklist