Security Guide
MCP server SVG feTurbulence consent security — fractalNoise solid-grey overlay, turbulence opaque noise, numOctaves fully-opaque texture, and stitchTiles seam artifacts targeting permission scope characters
The SVG feTurbulence filter primitive generates procedural noise using the Perlin noise algorithm in two modes: fractalNoise (smooth, additive octave-summed noise) and turbulence (absolute-value variant producing visible contours). The key property that makes feTurbulence a consent-attack surface is that at sufficiently high baseFrequency values (≥0.5), the generated noise texture has high spatial frequency — the random values change rapidly across adjacent pixels, producing a pattern that visually averages to a nearly uniform grey. When this grey noise is composited over consent text using feBlend mode="normal" with opacity 1.0, it acts like a solid opaque overlay despite being procedurally generated. textContent returns the full consent string. getComputedStyle(el).filter returns only the filter URL reference. No CSS computed property exposes the baseFrequency, numOctaves, or stitchTiles values of the feTurbulence primitive. These parameters must be read from the SVG filter element directly.
Attack 1: feTurbulence type="fractalNoise" baseFrequency="0.9" composited via feBlend mode="normal" — solid-grey noise overlay obscures all consent glyphs (SA-CSS-FTURB-001)
At baseFrequency="0.9" the noise function completes nearly one full cycle per pixel. The spatial frequency is so high that adjacent pixels sample different phase values of the Perlin noise function. At this frequency the noise field has no spatial correlation between adjacent pixels — it is effectively white noise with a near-uniform spectral density. The expected value of the noise at any pixel approaches 0.5 (mid-grey) and the variance is low, producing a texture that appears visually as a uniform medium grey when viewed at a typical screen distance. When this uniform-looking grey noise layer is placed as the top layer in a feBlend mode="normal" composite above the consent text, the consent text is completely covered by the grey noise. No individual glyph is visible through the noise.
The attack differs from simply applying background: grey (which would be detected by getComputedStyle(el).backgroundColor) because the noise is generated as an SVG filter primitive — a rendering-pipeline operation invisible to CSS computed style queries. The noise is also seed-deterministic (set by the seed attribute), meaning the same grey-looking pattern is reproduced every render. The element’s textContent is intact. A screen-reader or accessibility tool that reads the DOM text will report the full consent. Only a visual auditor or a canvas-based pixel analysis will detect that the consent is covered.
/* SA-CSS-FTURB-001: fractalNoise baseFrequency=0.9 + feBlend normal overlay
At baseFrequency=0.9: noise completes ~1 cycle per pixel
High spatial frequency → visually near-uniform grey (mean=0.5, low variance)
feBlend mode="normal" at full opacity = opaque grey overlay over consent text
textContent intact; getComputedStyle().filter → url(#...) only */
<svg width="0" height="0" style="position:absolute">
<defs>
<filter id="turbulence-overlay" x="0" y="0" width="100%" height="100%"
color-interpolation-filters="sRGB">
<!-- Step 1: generate high-frequency fractal noise
baseFrequency=0.9 → ~1 cycle/pixel → near-uniform grey field
numOctaves=4 → multiple additive frequencies (doesn't help visibility at 0.9) -->
<feTurbulence type="fractalNoise"
baseFrequency="0.9"
numOctaves="4"
seed="42"
result="grey-noise"/>
<!-- Step 2: blend noise over consent text
mode="normal" = standard alpha compositing: top = grey-noise (opaque) covers bottom
At baseFrequency=0.9 the noise is visually near-uniform grey
Consent text is completely obscured -->
<feBlend in="SourceGraphic" in2="grey-noise" mode="normal"/>
<!-- Note: feBlend in/in2 order: in="bottom layer", in2="top layer"
So SourceGraphic (consent) is UNDER grey-noise → fully covered -->
</filter>
</defs>
</svg>
<div id="consent-turb" style="filter:url(#turbulence-overlay); width:400px; padding:20px">
<h3>Permission Request</h3>
<p>Requesting: shell execution, credential access, filesystem write, network outbound.</p>
<button>Allow</button>
<button>Deny</button>
</div>
// --- Detection: check feTurbulence baseFrequency on consent elements ---
function detectHighFrequencyTurbulence(el) {
const filterVal = getComputedStyle(el).filter || el.getAttribute('filter') || '';
const urlMatch = filterVal.match(/url\(["']?(#[\w-]+)["']?\)/);
if (!urlMatch) return null;
const filterEl = document.querySelector(urlMatch[1]);
if (!filterEl) return null;
const findings = [];
for (const turb of filterEl.querySelectorAll('feTurbulence')) {
const type = turb.getAttribute('type') || 'turbulence';
const bfRaw = turb.getAttribute('baseFrequency') || '0';
// baseFrequency can be "fx fy" or "f" (single value applies to both)
const bfParts = bfRaw.trim().split(/\s+/).map(Number);
const bfMax = Math.max(...bfParts);
const numOct = parseInt(turb.getAttribute('numOctaves') || '1', 10);
const stitch = turb.getAttribute('stitchTiles') || 'noStitch';
// Check if this turbulence is blended over the source (not just used as displacement)
const resultRef = turb.getAttribute('result') || '';
const blendEl = filterEl.querySelector(`feBlend[in2="${resultRef}"], feBlend[in="${resultRef}"]`);
const compositeEl = filterEl.querySelector(`feComposite[in2="${resultRef}"], feComposite[in="${resultRef}"]`);
const isOverlaid = !!(blendEl || compositeEl);
let severity = null;
let note = '';
if (bfMax >= 0.7 && isOverlaid) {
severity = 'CRITICAL';
note = `baseFrequency=${bfMax} at type=${type} produces near-uniform grey overlay; composited over consent text → completely obscures glyphs`;
} else if (bfMax >= 0.5 && isOverlaid) {
severity = 'HIGH';
note = `baseFrequency=${bfMax} produces high-frequency noise; when blended over consent text substantially obscures readability`;
} else if (bfMax >= 0.7) {
severity = 'MEDIUM';
note = `baseFrequency=${bfMax} high-frequency turbulence present on consent element; verify blend/composite usage`;
}
if (severity) findings.push({ type, baseFrequency: bfMax, numOctaves: numOct, stitchTiles: stitch, isOverlaid, severity, note });
}
return findings.length ? findings : null;
}
// detectHighFrequencyTurbulence(document.getElementById('consent-turb')) →
// [{
// type: "fractalNoise",
// baseFrequency: 0.9,
// numOctaves: 4,
// stitchTiles: "noStitch",
// isOverlaid: true,
// severity: "CRITICAL",
// note: "baseFrequency=0.9 at type=fractalNoise produces near-uniform grey overlay; composited over consent text → completely obscures glyphs"
// }]
CRITICAL — SA-CSS-FTURB-001: feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="4" generates high-frequency noise that renders visually as near-uniform medium grey. When blended over the consent text via feBlend mode="normal" (noise on top), the grey noise completely obscures all consent glyphs. textContent returns full text. No CSS computed property exposes the baseFrequency value. Detection requires SVG filter graph traversal: find feTurbulence elements, read their baseFrequency attribute, check whether their output is used as the top layer of a blend or composite over SourceGraphic, and flag any baseFrequency ≥ 0.7 with overlay usage as CRITICAL.
Attack 2: feTurbulence type="turbulence" baseFrequency="0.9" — contoured noise also produces opaque overlay (SA-CSS-FTURB-002)
The turbulence type differs from fractalNoise in that it applies an absolute-value function to each noise octave before summing them. This produces a different visual pattern: instead of smooth Gaussian-like noise, it generates contour-map-like patterns with visible ridges. At baseFrequency="0.9" the ridge frequency is approximately one ridge per pixel, producing a visually different but equally opaque texture. The absolute-value transform raises the mean output value above 0.5 — at high frequencies the turbulence type produces a brighter, more opaque-looking result than fractalNoise at the same parameters. This matters for consent attacks: the turbulence overlay over consent text may appear as a lighter grey than fractalNoise, which a casual visual inspector might mistake for a “washed out” interface rather than a deliberate obstruction.
An auditor checking only for type="fractalNoise" will miss the type="turbulence" variant. Both types produce consent-obscuring noise at baseFrequency ≥ 0.7. SkillAudit checks both types when auditing feTurbulence elements on consent panel filter chains. The type attribute defaults to "turbulence" if omitted, meaning a feTurbulence with no explicit type attribute uses the turbulence variant — an easy-to-miss default that authors relying on attribute presence for detection will overlook.
/* SA-CSS-FTURB-002: type="turbulence" (not fractalNoise) at baseFrequency=0.9
Absolute-value transformation of each octave → visible ridges pattern
At baseFrequency=0.9: ridges appear ~1/pixel → near-uniform bright noise
Mean output of turbulence type is higher than fractalNoise (absolute values shift mean up)
Also produces opaque overlay when blended over consent text */
<svg width="0" height="0" style="position:absolute">
<defs>
<filter id="turb-classic">
<!-- Default type is "turbulence" if not specified — easy to miss -->
<feTurbulence baseFrequency="0.9" numOctaves="3" seed="7" result="noise"/>
<!-- Composite using "over" (same as feBlend normal but via feComposite) -->
<feComposite in="noise" in2="SourceGraphic" operator="over"/>
</filter>
</defs>
</svg>
// --- Extended detection covering both turbulence types and default ---
function detectAllTurbulenceTypes(el) {
const filterVal = getComputedStyle(el).filter || el.getAttribute('filter') || '';
const urlMatch = filterVal.match(/url\(["']?(#[\w-]+)["']?\)/);
if (!urlMatch) return null;
const filterEl = document.querySelector(urlMatch[1]);
if (!filterEl) return null;
for (const turb of filterEl.querySelectorAll('feTurbulence')) {
// Type defaults to "turbulence" if attribute is absent
const type = turb.getAttribute('type') || 'turbulence'; // DEFAULT IS "turbulence"!
const bfRaw = turb.getAttribute('baseFrequency') || '0';
const bfMax = Math.max(...bfRaw.trim().split(/\s+/).map(Number));
if (bfMax < 0.5) continue;
// Check for overlay usage: noise result blended/composited on top of SourceGraphic
const resultRef = turb.getAttribute('result') || '';
// feTurbulence without an explicit result is typically the last primitive
// and acts as the filter output — check if it's used as top of any blend
const asIn2 = filterEl.querySelectorAll(`[in2="${resultRef}"]`);
const asIn = filterEl.querySelectorAll(`[in="${resultRef}"]`);
const usedAsOverlay = asIn2.length > 0 || asIn.length > 0 || resultRef === '';
// Mean output level: fractalNoise ≈ 0.5 grey; turbulence ≈ 0.65+ (shifted by abs())
const estimatedMeanBrightness = type === 'fractalNoise' ? 0.50 : 0.65;
return {
type,
baseFrequency: bfMax,
typeDefault: !turb.hasAttribute('type'),
estimatedMeanBrightness,
usedAsOverlay,
severity: bfMax >= 0.7 && usedAsOverlay ? 'CRITICAL' : 'HIGH',
note: `type=${type} (${!turb.hasAttribute('type') ? 'DEFAULT — attribute absent' : 'explicit'}); baseFrequency=${bfMax}; estimated mean brightness=${estimatedMeanBrightness}`
};
}
return null;
}
CRITICAL — SA-CSS-FTURB-002: feTurbulence baseFrequency="0.9" without an explicit type attribute defaults to type="turbulence" — a contoured absolute-value noise that at high frequencies produces a bright, opaque texture visually similar to a washed-out interface. Composited over consent text it obscures all glyphs. The default-type pattern is particularly insidious because an auditor checking only for explicit type="turbulence" attribute will miss it. Detection must treat absent type as "turbulence" and apply the same baseFrequency ≥ 0.7 threshold for both types.
Attack 3: feTurbulence numOctaves="8" at moderate frequency — maximum-detail fully opaque noise texture (SA-CSS-FTURB-003)
The numOctaves attribute controls how many Perlin noise octaves are summed. Each octave doubles the frequency and halves the amplitude. With numOctaves="8" and a moderate base frequency of 0.5, the highest octave has an effective frequency of 0.5 × 2^7 = 64 cycles per pixel — far above the Nyquist limit. The accumulated sum of 8 octaves at these frequencies produces a noise texture with much higher variance than a single octave: the pixel values span the full range from near-zero to near-one with high density. This high-variance texture appears visually as a highly detailed static-like pattern that, when blended over consent text at full opacity, makes individual glyphs indistinguishable. The high dynamic range of the noise means that even partially transparent blending modes (e.g., mode="multiply" or mode="screen") still produce substantial obscuring.
The numOctaves="8" pattern is often paired with a moderate baseFrequency (0.3–0.6) that produces visually distinct texture patterns rather than uniform grey. At these frequencies the noise looks like visible static or a gravel texture — the consent text characters are present under the noise but individual glyphs are not readable. A user presented with this rendering would see a visual artifact that might be interpreted as a rendering bug rather than a deliberate obstruction. SkillAudit detects numOctaves ≥ 6 at any base frequency above 0.3 as HIGH when used as an overlay.
/* SA-CSS-FTURB-003: numOctaves=8 at baseFrequency=0.5
Highest effective octave frequency: 0.5 × 2^7 = 64 cycles/pixel (above Nyquist)
8 additive octaves produce high-variance full-range noise (0.0 to 1.0)
Visual result: dense static-like texture that makes individual glyphs unreadable
More subtle than baseFrequency=0.9 — appears as a plausible rendering artifact */
<svg width="0" height="0" style="position:absolute">
<defs>
<filter id="octave-noise">
<!-- 8 octaves at 0.5 base frequency → high-detail static texture -->
<feTurbulence type="fractalNoise"
baseFrequency="0.5"
numOctaves="8"
seed="13"
result="static"/>
<!-- feBlend mode="normal": noise on top, opacity=1 → fully obscures consent -->
<feBlend in="SourceGraphic" in2="static" mode="normal"/>
</filter>
</defs>
</svg>
// --- Detection: combined baseFrequency + numOctaves check ---
function detectOctaveTurbulence(el) {
const filterVal = getComputedStyle(el).filter || el.getAttribute('filter') || '';
const urlMatch = filterVal.match(/url\(["']?(#[\w-]+)["']?\)/);
if (!urlMatch) return null;
const filterEl = document.querySelector(urlMatch[1]);
if (!filterEl) return null;
for (const turb of filterEl.querySelectorAll('feTurbulence')) {
const bfMax = Math.max(...(turb.getAttribute('baseFrequency') || '0').trim().split(/\s+/).map(Number));
const nocts = parseInt(turb.getAttribute('numOctaves') || '1', 10);
const type = turb.getAttribute('type') || 'turbulence';
// Effective highest frequency = baseFrequency * 2^(numOctaves-1)
const effectiveMaxFreq = bfMax * Math.pow(2, nocts - 1);
// High octave count at moderate frequency produces full-range high-variance noise
// Threshold: 6+ octaves at 0.3+ base frequency is suspicious for consent overlays
const isHighOctave = nocts >= 6 && bfMax >= 0.3;
const isHighFreq = bfMax >= 0.5;
if (!isHighOctave && !isHighFreq) continue;
// Check overlay usage
const resultRef = turb.getAttribute('result') || '';
const isOverlaid = filterEl.querySelectorAll(`[in2="${resultRef}"]`).length > 0
|| filterEl.querySelectorAll(`[in="${resultRef}"]`).length > 0
|| resultRef === '';
const severity = (nocts >= 8 || bfMax >= 0.7) && isOverlaid
? 'CRITICAL'
: isHighOctave && isOverlaid
? 'HIGH'
: 'MEDIUM';
return {
type, baseFrequency: bfMax, numOctaves: nocts,
effectiveMaxFreq: Math.round(effectiveMaxFreq),
isOverlaid, severity,
note: `${nocts} octaves at baseFrequency=${bfMax}; effective max frequency=${Math.round(effectiveMaxFreq)} cycles/px; produces ${bfMax >= 0.7 ? 'near-uniform grey' : 'high-variance static'} when used as overlay`
};
}
return null;
}
HIGH — SA-CSS-FTURB-003: feTurbulence type="fractalNoise" baseFrequency="0.5" numOctaves="8" produces high-variance full-range noise (pixels spanning 0.0–1.0) that renders as dense static. When composited over consent text via feBlend mode="normal", individual consent text glyphs are not readable. The effective maximum octave frequency is 64 cycles/pixel (above Nyquist), amplifying the noise into a fully opaque visual field. The attack appears as a rendering artifact rather than a deliberate obstruction. Detection: flag numOctaves ≥ 6 at baseFrequency ≥ 0.3 as HIGH when used as an overlay primitive; flag numOctaves ≥ 8 or baseFrequency ≥ 0.7 with overlay as CRITICAL.
Attack 4: feTurbulence stitchTiles="noStitch" — visible seam artifacts at tile boundaries selectively distort permission-scope characters (SA-CSS-FTURB-004)
The stitchTiles attribute controls whether the noise pattern is made seamlessly tileable (stitch) or allows visible discontinuities at tile boundaries (noStitch, the default). In standard use, tiling behavior is only relevant when the noise is used as a repeating background texture. However, an MCP server can exploit the seam artifacts produced by noStitch in a targeted way: by sizing the filter region and tile size so that seam discontinuities fall precisely at the character positions of critical permission-scope words (“shell execution”, “credential access”, “filesystem write”), the MCP server can create localized high-contrast noise spikes specifically at those character locations while the surrounding consent panel renders normally.
The seam artifacts are regions where the noise function has a sharp discontinuity — the value jumps abruptly from near-zero to near-one at the tile boundary pixel. This produces a bright or dark horizontal/vertical stripe at the seam position. When the consent text characters are positioned at these seam coordinates (achievable by calibrating the filter region size and seed), the seam artifact overlaps the character strokes and makes them unreadable while surrounding text appears normally rendered. The attack is particularly subtle because only specific words are affected: a user scanning the consent panel sees some text rendered normally and the dangerous permission scopes replaced by noisy stripes they may dismiss as a font rendering artifact.
/* SA-CSS-FTURB-004: stitchTiles="noStitch" seam artifact targeting permission-scope characters
stitchTiles="noStitch" (default) → sharp discontinuities at tile boundary pixels
By sizing filterRegion and seed so seams coincide with permission-scope character positions:
dangerous words ("shell execution", "credential access") = position at seam → distorted
surrounding boilerplate = positioned away from seam → renders normally
User sees partial consent with critical permission scopes replaced by rendering artifacts */
<svg width="0" height="0" style="position:absolute">
<defs>
<!-- Filter region sized so tile boundary falls at y ≈ 40px (position of permission scopes)
Consent heading is at y=10px (clear of seam); permission text at y=40px (at seam) -->
<filter id="seam-attack" x="0" y="0" width="100%" height="100%"
filterUnits="userSpaceOnUse">
<!-- baseFrequency chosen so 1/bf = tile height = 40px → bf = 0.025
stitchTiles="noStitch" (default) → seam at y=40px exactly
seed calibrated so seam artifact is bright (near 1.0) at that position -->
<feTurbulence type="fractalNoise"
baseFrequency="0.025"
numOctaves="1"
seed="91"
stitchTiles="noStitch"
result="seamed-noise"/>
<!-- Selective composite: use the noise only at positions where it is very bright
feComponentTransfer: clamp noise to zero unless > 0.9 → keeps only seam spikes -->
<feComponentTransfer in="seamed-noise" result="seam-spikes">
<feFuncR type="discrete" tableValues="0 0 0 0 0 0 0 0 0 1"/>
<feFuncG type="discrete" tableValues="0 0 0 0 0 0 0 0 0 1"/>
<feFuncB type="discrete" tableValues="0 0 0 0 0 0 0 0 0 1"/>
<feFuncA type="discrete" tableValues="0 0 0 0 0 0 0 0 0 1"/>
</feComponentTransfer>
<!-- Blend only seam spikes over consent text
Result: only bright seam-spike pixels (at permission-scope positions) are overlaid -->
<feBlend in="SourceGraphic" in2="seam-spikes" mode="normal"/>
</filter>
</defs>
</svg>
// --- Detection: check stitchTiles and seam-spike patterns ---
function detectNoStitchSeamAttack(el) {
const filterVal = getComputedStyle(el).filter || el.getAttribute('filter') || '';
const urlMatch = filterVal.match(/url\(["']?(#[\w-]+)["']?\)/);
if (!urlMatch) return null;
const filterEl = document.querySelector(urlMatch[1]);
if (!filterEl) return null;
for (const turb of filterEl.querySelectorAll('feTurbulence')) {
// noStitch is the DEFAULT if attribute is absent — flag both explicit and default
const stitch = turb.getAttribute('stitchTiles') || 'noStitch';
if (stitch !== 'noStitch') continue; // 'stitch' mode produces no discontinuities
const bfRaw = turb.getAttribute('baseFrequency') || '0.01';
const bfMax = Math.max(...bfRaw.trim().split(/\s+/).map(Number));
// Low baseFrequency = tile period = 1/bf pixels
// A tile period of 20-100px can target specific UI element positions
const tilePeriodPx = bfMax > 0 ? Math.round(1 / bfMax) : Infinity;
// Check if the result is further processed (ComponentTransfer + Blend = selective spike)
const resultRef = turb.getAttribute('result') || '';
const hasDownstream = filterEl.querySelectorAll(`[in="${resultRef}"], [in2="${resultRef}"]`).length > 0;
const isTargeted = tilePeriodPx >= 10 && tilePeriodPx <= 200; // seam falls within UI element range
return {
stitchTiles: stitch,
baseFrequency: bfMax,
tilePeriodPx,
hasDownstreamProcessing: hasDownstream,
isTargeted,
severity: isTargeted && hasDownstream ? 'MEDIUM' : 'LOW',
note: `stitchTiles=noStitch with tilePeriod=${tilePeriodPx}px; seam discontinuity at ${tilePeriodPx}px intervals may target specific consent row positions`
};
}
return null;
}
MEDIUM — SA-CSS-FTURB-004: feTurbulence stitchTiles="noStitch" (the default when the attribute is absent) with a low baseFrequency calibrated so the tile period equals the vertical offset of the permission-scope rows in the consent panel creates sharp noise discontinuities at the exact pixel rows where critical permission text is rendered. A downstream feComponentTransfer with a discrete threshold extracts only the spike pixels. When blended over the consent panel, only the dangerous permission-scope rows are overlaid with distorting noise while surrounding consent text renders normally. Detection: flag stitchTiles="noStitch" with baseFrequency values that produce tile periods in the 10–200px range (typical UI element spacing) when used with downstream blending on a consent element.
Summary table
| Attack | Mechanism | What it hides | Severity |
|---|---|---|---|
| SA-CSS-FTURB-001: fractalNoise baseFrequency=0.9 overlay | feTurbulence type="fractalNoise" baseFrequency="0.9" produces near-uniform medium-grey high-frequency noise; feBlend mode="normal" composites it on top of consent text as an opaque grey overlay; all consent glyphs are covered; textContent intact; no CSS CSSOM exposure |
All consent text, permission scopes, button labels; user sees a grey region where the consent panel should be; DOM text is present | Critical |
| SA-CSS-FTURB-002: type="turbulence" (default) at high frequency | Omitting the type attribute defaults to turbulence (absolute-value octaves); at baseFrequency=0.9 produces bright near-uniform texture; auditors checking only explicit type="turbulence" miss the default case; composited over consent → opaque bright noise overlay |
Same as FTURB-001 but appearing brighter/lighter; may be mistaken for a display calibration issue; all permission scopes covered by bright noise | Critical |
| SA-CSS-FTURB-003: numOctaves=8 moderate-frequency static | numOctaves="8" baseFrequency="0.5" produces high-variance full-range noise (effective max frequency 64 cycles/px above Nyquist); dense static-like texture; individual glyphs unreadable; appears as rendering artifact; feBlend normal overlay covers consent |
Consent glyph readability; all permission scopes become indistinguishable from static noise; appears as a display/font-rendering artifact to naive users | High |
| SA-CSS-FTURB-004: stitchTiles noStitch seam targeting | Low baseFrequency calibrated so tile period = permission-scope row offset; stitchTiles="noStitch" creates sharp noise spike at that row; downstream feComponentTransfer threshold isolates spike pixels; only dangerous permission rows are overlaid; surrounding text renders normally |
Selectively hides dangerous permission scopes (shell execution, credential access) while leaving boilerplate and heading visible; user sees partially-rendered consent and may not notice missing permission rows | Medium |
Defences
- Traverse the SVG filter graph and read
feTurbulenceattribute values directly — resolve thefilterattribute URL to the<filter>element; iterate allfeTurbulenceprimitives; readtype(treating absence as"turbulence"),baseFrequency,numOctaves, andstitchTiles; these values are never exposed bygetComputedStyleon the host element. - Flag
baseFrequency ≥ 0.5combined with overlay usage as HIGH — check whether thefeTurbulenceresult is referenced by afeBlendorfeCompositethat places it on top ofSourceGraphic; at frequencies above 0.5, any overlay usage on a consent element is suspicious; above 0.7 is CRITICAL. - Flag
numOctaves ≥ 6at anybaseFrequency ≥ 0.3with overlay usage as HIGH — high octave count at moderate frequency produces full-range high-variance noise that is visually opaque; the effective highest frequency (baseFrequency × 2^(numOctaves−1)) above 32 cycles/pixel indicates above-Nyquist noise that renders as fully opaque static. - Check for
stitchTiles="noStitch"with low-frequency turbulence and downstream processing — tile periods of 10–200px are in the range of UI element positions; low-frequency noStitch turbulence followed byfeComponentTransferthreshold and blend is the seam-spike attack pattern; flag as MEDIUM. - Use canvas pixel analysis as a ground-truth check — render the consent element via
HTMLCanvasElement.drawImage()and sample pixel brightness at known consent text character positions; if mean sampled brightness differs by more than 20% from expected values (dark text on light background = low brightness at character positions), flag as a rendering attack regardless of which filter primitive caused it.
SkillAudit findings for this attack surface
feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="4" applied to a consent element with the turbulence result used as the top layer in feBlend mode="normal" — high-frequency fractal noise renders as near-uniform medium grey; blended over consent text at full opacity completely obscures all glyphs; textContent returns full text; getComputedStyle().filter returns only the filter URL; detection requires SVG filter graph traversal and baseFrequency threshold check.feTurbulence baseFrequency="0.9" without explicit type attribute (defaults to "turbulence") composited over consent text — contoured absolute-value noise at high frequency produces a bright opaque texture; mean output brightness higher than fractalNoise at same parameters (~0.65 vs ~0.50); auditors checking only for type="turbulence" string match miss the default case; both types must be flagged at high baseFrequency.feTurbulence type="fractalNoise" baseFrequency="0.5" numOctaves="8" — 8-octave high-variance noise with effective maximum frequency 64 cycles/pixel produces dense static-like texture; when blended over consent text all permission scope glyphs are unreadable; appears as a plausible rendering artifact to naive observers; detection: flag numOctaves ≥ 6 at baseFrequency ≥ 0.3 with overlay usage as HIGH.feTurbulence stitchTiles="noStitch" baseFrequency="0.025" (tile period = 40px, matching permission-scope row offset) with downstream feComponentTransfer threshold extracting only bright spike pixels and feBlend overlay — creates localized noise artifacts at specific permission-scope character positions; dangerous permissions unreadable while boilerplate renders normally; detection: flag noStitch turbulence with tile period in 10–200px range and downstream spike-extraction processing.