MCP server SVG feTurbulence security
The SVG <feTurbulence> primitive generates Perlin noise (type="turbulence") and fractal noise (type="fractalNoise") textures. It is commonly used for paper grain, cloud backgrounds, water ripple effects, and generative art. When feTurbulence output is placed as the foreground layer in an feMerge stack over consent text, or composited as an opaque overlay, the noise texture completely covers the disclosure text — while appearing to be a legitimate loading state, scanning animation, or visual styling effect. This page covers four feTurbulence consent attack patterns including a standalone overlay, a loading-state mask, a Perlin noise pattern attack, and a SMIL-triggered baseFrequency animation.
Finding SA-FTURB-001: fractalNoise high-frequency overlay as top feMerge node covers consent text
<feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="4" seed="2"> produces a high-frequency noise texture with fine-grained variation at near-pixel scale. At baseFrequency=0.65, the noise pattern has a characteristic feature size of approximately 1/0.65 ≈ 1.5 pixels — much smaller than the 14px consent text glyphs. The noise output is an RGBA image with random values at each pixel. When this output is placed as the final (topmost) <feMergeNode> in a <feMerge> stack — above the feMergeNode in="SourceGraphic" — the noise completely covers the consent text. The alpha channel of fractalNoise output is also random; where alpha is high, the noise pixel is opaque and covers the text pixel beneath. The visual result looks like a grainy static texture or paper grain overlay — not obviously an attack. No feFlood, no feBlend, no feComposite — the attack uses feTurbulence + feMerge ordering.<defs>
<filter id="noiseOverlay">
<!-- fractalNoise at high baseFrequency generates fine-grained opaque noise texture -->
<feTurbulence type="fractalNoise"
baseFrequency="0.65"
numOctaves="4"
seed="2"
result="noiseOut"/>
<feMerge>
<!-- SourceGraphic first (below) -->
<feMergeNode in="SourceGraphic"/>
<!-- noiseOut second (on top): covers consent text pixels -->
<feMergeNode in="noiseOut"/>
</feMerge>
</filter>
</defs>
<text x="20" y="50" font-size="14" fill="#111827"
filter="url(#noiseOverlay)">
I authorize this MCP server to access all file system paths
and transmit captured data to external endpoints.
</text>
Detection: scan feMerge stacks for an feTurbulence-sourced feMergeNode that is not the first (bottom) node. If the turbulence result appears in a non-first feMergeNode position and its effective average alpha over the consent text area exceeds 0.4 (high-frequency fractalNoise with numOctaves≥3 typically has mean alpha ≈ 0.5), flag Critical. The stacking-order check for feTurbulence parallels the feFlood feMerge stacking order detection in feMerge ordering attacks.
Finding SA-FTURB-002: feTurbulence as a "loading/scanning" state mask — social engineering evasion
baseFrequency="0.02" numOctaves="2") combined with a feColorMatrix type="saturate" values="0" to desaturate and a slight bright bias produces a smooth gray-cloud texture. When this texture is applied to the consent text element's filter and animated via a SMIL animate on the seed attribute (incrementing the seed over time creates a slowly shifting noise pattern), the effect visually resembles a "loading" or "processing" animation — a common UI pattern that users expect to resolve before they interact. The user may wait for the perceived loading animation to finish; the animation never finishes. During the entire wait, the consent text is covered by the noise texture. The attack combines visual social engineering (looks like a loading state) with filter-based erasure.<defs>
<filter id="loadingMask">
<!-- Low-frequency fractalNoise looks like a "loading" animation -->
<feTurbulence type="fractalNoise"
baseFrequency="0.02"
numOctaves="2"
seed="1"
result="cloudNoise">
<!-- Animate seed to make the texture shift slowly -->
<animate attributeName="seed"
from="1" to="20"
dur="5s"
repeatCount="indefinite"/>
</feTurbulence>
<!-- Desaturate to gray and brighten -->
<feColorMatrix in="cloudNoise" type="saturate" values="0" result="grayNoise"/>
<feComponentTransfer in="grayNoise" result="brightNoise">
<feFuncR type="linear" slope="0.4" intercept="0.6"/>
<feFuncG type="linear" slope="0.4" intercept="0.6"/>
<feFuncB type="linear" slope="0.4" intercept="0.6"/>
<feFuncA type="linear" slope="1" intercept="0"/>
</feComponentTransfer>
<feMerge>
<feMergeNode in="SourceGraphic"/>
<feMergeNode in="brightNoise"/>
</feMerge>
</filter>
</defs>
Detection: the desaturate + brighten + animate pipeline produces a bright gray overlay that covers consent text. Evaluate the feFuncR/G/B intercept values: intercept=0.6 means even a zero-input pixel outputs at 0.6 brightness — opaque gray over white background produces near-zero text contrast. The animation on seed attribute is a secondary signal: legitimately loading states don't need to cover consent text. Flag High when feTurbulence result is in a foreground merge node and its post-pipeline brightness would produce WCAG contrast below 3:1 on the text underneath.
Finding SA-FTURB-003: type=turbulence Perlin noise composited over consent text as feComposite foreground
type="turbulence") at medium baseFrequency produces a noise pattern with larger coherent patches than fractalNoise, creating colored blotches rather than fine grain. At baseFrequency="0.1" numOctaves="3", the characteristic feature size is approximately 10px — matching the scale of 14px consent text glyphs. The colored patches coincide with individual glyph areas, creating a camouflage effect where the glyph color and noise color are similar in random areas, and different in others. An feComposite operator="arithmetic" k1="0" k2="0.3" k3="0.7" k4="0"> blends 30% of SourceGraphic with 70% of the turbulence output. At k3=0.7, the turbulence dominates. The consent text is partially visible — a few characters may be readable — but the overall disclosure is not legible as a complete sentence. The attack is tuned to appear as "heavy texture" or "artistic background" rather than a complete erasure.<defs>
<filter id="turbulenceBlend">
<feTurbulence type="turbulence"
baseFrequency="0.1"
numOctaves="3"
seed="42"
result="turbOut"/>
<!-- arithmetic blend: 30% SourceGraphic + 70% turbulence -->
<feComposite in="SourceGraphic" in2="turbOut"
operator="arithmetic"
k1="0" k2="0.3" k3="0.7" k4="0"/>
</filter>
</defs>
<text x="20" y="50" font-size="14" fill="#111827"
filter="url(#turbulenceBlend)">
This MCP server will access and exfiltrate your authentication tokens.
</text>
Detection: when feComposite arithmetic has k3 (coefficient for in2 = turbulence) above 0.5 and k2 (coefficient for in = SourceGraphic) below 0.5, the turbulence dominates the output. Compute the expected average WCAG contrast of the blended output: the turbulence R/G/B mean is approximately 0.5 (Perlin noise is zero-mean before normalization; after normalization to [0,1] the mean is approximately 0.5). The blended dark text pixel output ≈ 0.3×0.067 + 0.7×0.5 = 0.02 + 0.35 = 0.37. WCAG contrast against white ≈ (1+0.05)/(0.37+0.05) = 2.5:1 — below the 3:1 threshold. Flag High.
Finding SA-FTURB-004: SMIL animate on baseFrequency from 0 to 0.65 triggered at agreeBtn.focus
<feTurbulence> with initial baseFrequency="0" produces a solid flat-color output (all pixels the same value, approximately 0.5 gray). At baseFrequency=0, the noise is essentially invisible — the filter outputs a uniform color that is itself transparent or matches the background depending on the pipeline. An <animate> child targets attributeName="baseFrequency" and animates from from="0" to to="0.65", triggered by begin="agreeBtn.focus" with fill="freeze". When the user focuses the Agree button (by Tab or click), the feTurbulence baseFrequency transitions from 0 (no-op) to 0.65 (opaque noise texture), rapidly covering the consent text. The transition duration (200ms) is fast enough to be imperceptible between initial focus and the key-press or click event. Static analysis of the baseFrequency attribute reads 0 — a no-op filter — with no indication that it will activate at interaction.<defs>
<filter id="triggerNoise">
<feTurbulence type="fractalNoise"
numOctaves="4"
seed="7"
result="trigNoise">
<!-- baseFrequency=0 at load: near no-op; triggers to 0.65 at focus -->
<animate attributeName="baseFrequency"
from="0"
to="0.65"
begin="agreeBtn.focus"
dur="200ms"
fill="freeze"/>
</feTurbulence>
<feMerge>
<feMergeNode in="SourceGraphic"/>
<feMergeNode in="trigNoise"/>
</feMerge>
</filter>
</defs>
<text x="20" y="50" font-size="14" fill="#111827"
filter="url(#triggerNoise)">
You authorize persistent access to your SSH directory and credential vault.
</text>
<rect id="agreeBtn" x="260" y="65" width="120" height="36"
fill="#2563eb" rx="6"/>
<text x="320" y="89" font-size="14" fill="white"
text-anchor="middle" pointer-events="none">Agree</text>
Detection: scan for <animate> children on <feTurbulence> elements in consent-text filters. Read the to attribute for baseFrequency. If the post-trigger baseFrequency is above 0.05 and the feTurbulence result is in a foreground merge position or composite foreground, compute the predicted post-trigger opacity coverage. Correlation with interaction-event begin attribute is required. A static check on baseFrequency=0 incorrectly classifies the filter as a no-op; the attack value is in the animate to attribute — the same detection pattern as SMIL-triggered attacks on feFlood flood-opacity and feGaussianBlur stdDeviation. Flag Critical when post-trigger coverage would produce WCAG contrast below 3:1 on consent text.
Detection algorithm: feTurbulence consent overlay
| Step | Action | Catches |
|---|---|---|
| 1 | Identify all feTurbulence primitives in filters applied to consent text elements. Read type, baseFrequency, numOctaves, seed | Establishes turbulence parameters |
| 2 | Check the position of the feTurbulence result in downstream feMerge stacks. If it appears in any non-first feMergeNode position and baseFrequency > 0.05, estimate average alpha coverage (high-freq fractalNoise ≈ 0.5, low-freq turbulence ≈ 0.35). Flag Critical if coverage × opacity > 0.4 over consent text area | SA-FTURB-001, SA-FTURB-002: top-layer noise overlay |
| 3 | Check feComposite arithmetic k3 coefficient for in2=turbulence result. If k3 > 0.5, turbulence dominates. Compute predicted blended contrast. Flag High if below 3:1 | SA-FTURB-003: Perlin turbulence composite blend |
| 4 | Scan animate children on feTurbulence for attributeName="baseFrequency". Read to value. If post-trigger baseFrequency > 0.05 and turbulence result is in foreground position, correlate begin with interaction events. Flag Critical | SA-FTURB-004: SMIL animate on baseFrequency at interaction |
SkillAudit checks every feTurbulence primitive in consent-element filters: stacking order in feMerge, feComposite blend coefficients, animate children on baseFrequency and seed, and predicted visual coverage. Run a free audit on your MCP server's GitHub URL.