← Blog

MCP server SVG feTurbulence fractalNoise consent security

Four SA-CSS-FRA consent attack patterns using feTurbulence type="fractalNoise" — the signed Perlin noise variant that produces smooth large-scale displacement fields with long correlation lengths, enabling entire consent panels to be displaced off their element bounds without visible pixel-level artifacts that would signal an attack.

By SkillAudit  |  23 Oct 2026

The feTurbulence SVG filter primitive generates a procedural noise texture using Perlin noise. The type attribute selects between two noise variants: type=turbulence (absolute-valued noise, with visible creases at zero crossings) and type=fractalNoise (signed noise, without absolute value, producing smooth continuous variation). This distinction is the critical security difference for consent attacks.

When feTurbulence output is used as a displacement field via feDisplacementMap, the type choice determines whether the displacement has sharp folds (turbulence) or smooth gradients (fractalNoise). Sharp folds create visible text fragmentation artifacts that can alert a user. Smooth gradients create a gentle drift of the entire consent element — indistinguishable from a layout shift at the resolution of human perception.

The fractalNoise advantage for attackers: type=turbulence applies abs() to the raw Perlin output before rescaling to [0,1]. This creates zero crossings where the displacement field folds — visible as sharp discontinuities in displaced text. type=fractalNoise omits the abs(), mapping the signed Perlin output (range [−1, 1]) directly to [0, 1] by rescaling. No zero crossings, no folds, no discontinuities. At low base frequencies, the entire displacement varies smoothly across the element — the consent text drifts as a unit.

SA-CSS-FRA-001: low baseFrequency + high displacement scale (macro drift off element)

The correlation length of Perlin noise is approximately the reciprocal of baseFrequency. At baseFrequency=0.005, the correlation length is 1/0.005 = 200 pixels. For a consent panel of 400×80 pixels, the noise varies by at most half a period (200 pixels) across the element width — meaning the displacement direction is approximately uniform across the panel and the panel moves as a unit.

With feDisplacementMap scale=1000, the displacement is up to ±500 pixels (scale × (noise − 0.5), where noise is in [0,1]). A consent panel of width 400px displaced by 500px horizontally exits the element's visible region entirely. After the element's filter region clips the displaced output, the consent text is no longer visible within the element's layout box.

<filter id="fra1">
  <feTurbulence type="fractalNoise" baseFrequency="0.005" numOctaves="1"
                seed="42" result="noise"/>
  <feDisplacementMap in="SourceGraphic" in2="noise"
                     scale="1000" xChannelSelector="R" yChannelSelector="G"/>
</filter>
<!-- correlation length = 200px; panel moves as unit; scale=1000 → ±500px shift; text off-bounds -->

The BCR (getBoundingClientRect) of the element is unchanged — the element still occupies its layout box. DOM presence, textContent, and all JS property reads show the consent element normally. The filter displacement moves the rendered pixels, not the layout geometry.

SA-CSS-FRA-002: high numOctaves adds fine detail without defeating macro displacement

With numOctaves=8, feTurbulence sums 8 octaves of noise: the base frequency (0.005) plus 7 higher harmonics (0.01, 0.02, 0.04, 0.08, 0.16, 0.32, 0.64). The higher octaves add fine-grained texture to the displacement field, but the low-frequency base still dominates the large-scale displacement direction. The consent panel still drifts as a unit; the fine-detail octaves add slight local distortion within the panel's text but do not fragment it.

<feTurbulence type="fractalNoise" baseFrequency="0.005" numOctaves="8" seed="42"/>

Evasion: high numOctaves at low baseFrequency is a common pattern in legitimate texture-generation SVG (leather, cloud, fabric simulations). A filter with numOctaves=8 looks like a texture effect to a casual inspector; the displacement scale attribute on feDisplacementMap is where the attack magnitude lives.

SA-CSS-FRA-003: seed-targeted displacement direction

The seed attribute determines the phase of the Perlin noise gradient. Different seed values produce different displacement directions across the same element geometry. An attacker pre-computes the displacement field for each seed and selects the seed that pushes the consent panel in a specific direction — typically toward the element's right edge (off the layout box for LTR layouts) or upward (above the visible viewport for top-anchored dialogs).

<!-- Attacker pre-computes: at 400x80px, seed=7 → average displacement (R channel) ≈ 0.87
  → displacement = 1000×(0.87-0.5) = +370px → consent panel pushed right, off element -->
<feTurbulence type="fractalNoise" baseFrequency="0.005" numOctaves="1" seed="7"/>

The seed value is just an integer — attribute-range validators will not flag any specific seed. Detection requires evaluating the spatial average of the noise output at the target element's dimensions, which requires actually running the Perlin generator or a numerical approximation.

SA-CSS-FRA-004: fractalNoise vs turbulence — artifact comparison

For the same baseFrequency=0.05 and scale=300, type=turbulence applies abs() to the raw noise before rescaling. At zero crossings of the raw noise, the abs() function creates cusps — the displacement field changes direction sharply, and text displaced across a cusp is visibly torn/fragmented. These fragments are obvious to a user reading the consent element.

Type=fractalNoise omits abs(), so the displacement field is smooth everywhere. Text displaced by fractalNoise appears to slide uniformly, not fragment. At the same scale, the fractalNoise version is visually more suspicious as a "misaligned UI element" rather than a "broken rendering artifact."

<!-- turbulence: visible creases at zero crossings → detectable by user -->
<feTurbulence type="turbulence" baseFrequency="0.05" scale="300"/>

<!-- fractalNoise: smooth displacement → looks like layout drift to user -->
<feTurbulence type="fractalNoise" baseFrequency="0.05" scale="300"/>

Detection systems that analyze feTurbulence solely by baseFrequency and scale thresholds must apply the same thresholds to both type=turbulence and type=fractalNoise. The type attribute does not change whether the displacement is dangerous — only how visually obvious it is to users.

Attack summary

IDtypebaseFrequencyscaleEffectSeverity
SA-CSS-FRA-001fractalNoise0.0051000Macro drift; panel displaced off element boundsCritical
SA-CSS-FRA-002fractalNoise0.0051000Same + texture detail; looks like legitimate textureCritical
SA-CSS-FRA-003fractalNoise0.0051000Seed-targeted directional push off specific edgeCritical
SA-CSS-FRA-004fractalNoise0.05300Smooth displacement vs. type=turbulence fragmentation artifactsHigh

Defences

SkillAudit findings for this attack surface

CRITICAL SA-CSS-FRA-001: feTurbulence type="fractalNoise" baseFrequency="0.005" + feDisplacementMap scale="1000" — correlation length 200px; consent panel displaced as unit; max displacement ±500px; panel exits element layout bounds; no pixel-level fragmentation artifacts; BCR and DOM unchanged.
CRITICAL SA-CSS-FRA-002: same pattern with numOctaves="8" — high octave count mimics legitimate texture filter appearance; base frequency still dominates macro displacement; fine-detail octaves add local distortion but do not prevent panel-level drift; evasion via texture appearance.
CRITICAL SA-CSS-FRA-003: seed-targeted fractalNoise displacement — specific seed pre-computed to produce displacement direction pushing consent panel toward a specific element edge; scale=1000 + seed=7 (for 400px element) → average R-channel output pushes panel off right edge; seed attribute is just an integer, not detectable by attribute-range check.
HIGH SA-CSS-FRA-004: type=fractalNoise chosen over type=turbulence for smooth displacement — same scale produces smooth drift (fractalNoise) vs. visible text fragmentation (turbulence); attacker specifically uses fractalNoise to eliminate user-visible artifact clues; detection must apply scale-threshold check to both types equally.

Related: feTurbulence seed attacks  |  feTurbulence numOctaves attacks  |  feDisplacementMap scale attacks  |  feTurbulence anisotropic displacement

← Blog  |  Security Checklist