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.
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
| ID | type | baseFrequency | scale | Effect | Severity |
|---|---|---|---|---|---|
| SA-CSS-FRA-001 | fractalNoise | 0.005 | 1000 | Macro drift; panel displaced off element bounds | Critical |
| SA-CSS-FRA-002 | fractalNoise | 0.005 | 1000 | Same + texture detail; looks like legitimate texture | Critical |
| SA-CSS-FRA-003 | fractalNoise | 0.005 | 1000 | Seed-targeted directional push off specific edge | Critical |
| SA-CSS-FRA-004 | fractalNoise | 0.05 | 300 | Smooth displacement vs. type=turbulence fragmentation artifacts | High |
Defences
- Detect feDisplacementMap with feTurbulence input: For any feDisplacementMap in a consent ancestor filter, trace in2 to its source primitive. If the source is a feTurbulence, compute the effective maximum displacement: |scale| / 2 (since noise range is [0,1], max offset from center is scale × 0.5). If max displacement > half the element's width or height, flag as macro-displacement attack.
- Type-independent detection: Apply the same scale-threshold check to both type=turbulence and type=fractalNoise. The type attribute affects visual artifact detectability for users, not attack severity. Use scale × 0.5 > element_dimension / 2 as the threshold (panel can be pushed off-edge).
- Low-baseFrequency amplification: For baseFrequency < 0.01 combined with any feDisplacementMap, apply reduced scale thresholds. Low frequency concentrates displacement energy in long-wave components that push the element as a unit; the attack is more effective at low baseFrequency with moderate scale than at high baseFrequency with high scale.
- MutationObserver on feTurbulence seed: An attacker can change the seed attribute dynamically to rotate the displacement direction. Monitor seed attribute changes on feTurbulence elements in consent ancestor filters.
- Render and measure: The definitive detection is rendering the filtered element to an offscreen canvas and measuring whether any consent text content remains within the element's layout bounds (BCR region). Text that has been displaced entirely outside the BCR region is absent from the rendered area — regardless of the DOM textContent.
SkillAudit findings for this attack surface
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.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.