MCP server CSS feDiffuseLighting security: diffuseConstant overdrive white saturation, feDistantLight directional positioning over acceptance clause, feBlend screen illumination wash, and CSS lighting-color white background-matching illumination of consent text
Published 2026-09-26 — SkillAudit Research
The SVG feDiffuseLighting filter primitive implements the Lambertian diffuse component of the Phong lighting model. The diffuse intensity at each pixel is: diffuseConstant × dot(N, L), where N is the surface normal derived from the height-map input and L is the normalized direction to the light source. Unlike feSpecularLighting which produces narrow angular hotspots, feDiffuseLighting uses the Lambertian model — diffuse contribution is maximum when the surface faces the light directly and falls off as a cosine with increasing angle. This produces broad, smooth illumination over the entire light-facing region rather than a localized hotspot.
The diffuseConstant attribute multiplies the diffuse contribution before it is added to the output. At diffuseConstant=1, the output is bounded to [0, 1] for correctly illuminated pixels; at diffuseConstant > 1, all but the most obliquely lit pixels produce output exceeding 1.0. When this overdriven diffuse output is composited via feComposite operator="arithmetic", values exceeding 1.0 are clipped to 1.0 — the maximum channel value — producing a white wash over the entire illuminated region. This differs from feSpecularLighting's narrow hotspot: diffuse overdrive washes a large fraction of the consent text area to white, not just a targeted word.
Broad vs. narrow erasure: feDiffuseLighting with high diffuseConstant produces wide-area erasure; feSpecularLighting with high specularExponent produces narrow hotspot erasure. Both use the same lighting infrastructure (light source child elements, lighting-color, height-map input) but differ in spatial coverage. A broad diffuse wash is more aggressive — it erases most or all of the consent text rather than individual words — but may be more visually obvious to a human reviewer because the large white patch is harder to explain as an aesthetic effect.
Attack findings
The Lambertian diffuse model produces maximum output (dot(N, L) = 1.0) for pixels where the surface normal points directly toward the light. At
diffuseConstant=5, these pixels produce an output value of 5.0 in each channel — far exceeding the maximum 1.0. When composited via feComposite operator="arithmetic" k2="1" k3="1" k4="0" (which adds the diffuse output to SourceGraphic and clips to [0,1]), the consent text pixels in the illuminated region are replaced by clipped white output. At diffuseConstant=5, only pixels where dot(N,L) < 0.2 (highly oblique surfaces, i.e., shadow regions) produce sub-1.0 output. For smooth text on a flat surface, essentially all pixels facing the light are overdriven to white.
<filter id="diffuse-overdrive">
<feGaussianBlur in="SourceAlpha" stdDeviation="4" result="height-map"/>
<!-- diffuseConstant=5: output = 5 × dot(N,L)
For nearly all front-facing pixels: dot(N,L) > 0.2 → output > 1.0
lighting-color: #ffffff (white illumination) -->
<feDiffuseLighting in="height-map" diffuseConstant="5"
lighting-color="#ffffff" result="diffuse-out">
<feDistantLight azimuth="45" elevation="60"/>
</feDiffuseLighting>
<!-- feComposite arithmetic: clips channel values to [0,1] -->
<!-- Adds diffuse white to SourceGraphic: text + overdrive = white -->
<feComposite in="diffuse-out" in2="SourceGraphic"
operator="arithmetic" k2="1" k3="1" k4="0"/>
</filter>
<!-- Diffuse contribution at illuminated text pixel:
diffuseConstant=5, dot(N,L)=0.8 (typical front-facing pixel)
diffuse output: 5 × 0.8 = 4.0 per channel → clipped to 1.0 (white)
feComposite arithmetic(k2=1, k3=1, k4=0): k2×A + k3×B + k4
A=diffuse (1.0 white), B=SourceGraphic text (#1a1a1a = 0.106)
Output: 1×1.0 + 1×0.106 + 0 = 1.106 → clipped to 1.0 = white
Result: all illuminated consent text pixels → white
Contrast against white background: 1:1 → completely invisible -->
feDistantLight is a directional light source (infinite distance) parameterized by azimuth (horizontal angle) and elevation (vertical angle). The direction of maximum Lambertian illumination is determined by the light direction vector derived from azimuth and elevation. The height-map normal at smooth flat surfaces points primarily in the +Z direction; the illumination is maximized when the light direction is also +Z (elevation=90°). By controlling azimuth and elevation, the attacker controls which portions of a curved or slanted height-map surface receive maximum diffuse illumination. For consent text rendered with specific font glyphs and shapes, the attacker can calibrate azimuth/elevation to maximize dot(N,L) at the pixel positions corresponding to the acceptance clause words, ensuring those words receive the highest diffuse contribution (and thus the most aggressive clipping to white).
<filter id="targeted-diffuse">
<!-- Height map from SourceAlpha: text glyph normals point slightly in stroke direction -->
<feGaussianBlur in="SourceAlpha" stdDeviation="2" result="height"/>
<feDiffuseLighting in="height" diffuseConstant="3"
lighting-color="#f0f0f0" result="targeted-diffuse">
<!-- feDistantLight: azimuth/elevation calibrated for maximum coverage of "agree" -->
<!-- azimuth=0 (light from +X direction) → maximum on right-facing glyph normals -->
<!-- elevation=75 (nearly vertical) → near-maximum on all front-facing pixels -->
<feDistantLight azimuth="0" elevation="75"/>
</feDiffuseLighting>
<feBlend in="targeted-diffuse" in2="SourceGraphic" mode="screen"/>
</filter>
<!-- Lambertian illumination at elevation=75:
Light direction: approximately (0, sin(15°), cos(15°)) = (0, 0.26, 0.97)
Surface normal for flat height map: approximately (0, 0, 1)
dot(N, L) ≈ cos(15°) ≈ 0.97 → high diffuse for all flat-surface pixels
diffuseConstant=3: 3 × 0.97 = 2.9 → exceeds 1.0 → clips to white
feBlend screen with near-white diffuse over dark text → text bleached -->
The
feBlend mode="screen" formula is 1 - (1-A)(1-B). When feDiffuseLighting provides the in source (A) and SourceGraphic provides in2 (B), and the diffuse output has significant non-zero values across the text region, the screen blend lightens the output toward white. For a diffuse contribution value of 0.7 (background-matching near-white diffuse output) blended with dark text (0.106): screen(0.7, 0.106) = 1 - (1-0.7)(1-0.106) = 1 - 0.3 × 0.894 = 0.732. The text pixel becomes medium-light gray (0.73 ≈ #bababa) rather than dark (#1a1a1a). Against a white background (1.0), the contrast is 1.0 / 0.732 ≈ 1.37:1 — well below the 3:1 WCAG minimum. The diffuse light's broad coverage means this effect applies across the entire consent text area rather than a targeted spot.
<filter id="diffuse-screen-wash">
<feGaussianBlur in="SourceAlpha" stdDeviation="3" result="hmap"/>
<!-- Broad diffuse light: large area illumination across consent text -->
<feDiffuseLighting in="hmap" diffuseConstant="1.5"
lighting-color="#e8e8e8"
result="broad-diffuse">
<feDistantLight azimuth="45" elevation="80"/>
</feDiffuseLighting>
<!-- feBlend screen: diffuse output bleaches dark text -->
<feBlend in="broad-diffuse" in2="SourceGraphic" mode="screen"/>
</filter>
<!-- diffuse output (near-white, value ≈ 0.91 for #e8e8e8 normalized):
screen(0.91, text_pixel=0.106):
= 1 - (1-0.91)(1-0.106) = 1 - 0.09 × 0.894 = 0.920
Text pixel output: ≈ 0.920 = #eaeaea (near-white)
Background: 1.0 (#ffffff)
Contrast: 1.0 / 0.920 ≈ 1.09:1 → far below 3:1 → text INVISIBLE
DOM audit: fill=#1a1a1a (non-transparent) ✓, filter=url(...) visible in DOM ✓
Only rendering output analysis detects 1.09:1 contrast -->
The CSS
lighting-color property sets the color of the light source for feDiffuseLighting and feSpecularLighting. When set to #ffffff (white) via CSS (overriding an SVG attribute value), the diffuse contribution is in white — a neutral, apparently legitimate illumination color. However, white light added to dark consent text via a feComposite or feBlend brightens the text pixels. The CSS override pattern makes this harder to detect: the SVG attribute may read a neutral value; only getComputedStyle reveals the CSS-overridden white lighting-color. Combined with a modest diffuseConstant (1.5–3), the illumination adds enough white to dark text pixels to push them from the low-luminance dark range to the mid-luminance range, reducing the contrast ratio against the white background from above 4:1 to below 3:1.
<!-- SVG attribute: lighting-color="#808080" (neutral gray — innocuous) -->
<!-- CSS override: .consent-filter { lighting-color: #ffffff } -->
<!-- getAttribute returns "#808080"; getComputedStyle returns "#ffffff" -->
<filter id="css-lighting-override" class="consent-filter">
<feGaussianBlur in="SourceAlpha" stdDeviation="2" result="hmap"/>
<!-- Attribute: lighting-color="#808080" (neutral gray, passes attribute audit) -->
<!-- CSS class overrides to lighting-color:#ffffff (white light) -->
<feDiffuseLighting in="hmap" diffuseConstant="2" lighting-color="#808080"
result="white-diffuse">
<feDistantLight azimuth="45" elevation="70"/>
</feDiffuseLighting>
<feBlend in="white-diffuse" in2="SourceGraphic" mode="screen"/>
</filter>
<!-- Attribute read: lighting-color="#808080" → neutral gray → audit PASSES
CSS computed: lighting-color="#ffffff" → white light → actual rendering
With white lighting-color, diffuseConstant=2, elevation=70:
dot(N,L) ≈ 0.94 → diffuse = 2 × 0.94 = 1.88 → clipped to 1.0 (white)
feBlend screen(1.0, dark-text) = 1 - (1-1.0)(1-text) = 1 - 0 = 1.0 → white
Entire illuminated region: white → invisible on white background -->
Detection
function checkFeDiffuseLighting(svgRoot) {
const findings = [];
const diffEls = svgRoot.querySelectorAll('feDiffuseLighting');
for (const diff of diffEls) {
const diffConst = parseFloat(diff.getAttribute('diffuseConstant') || '1');
const resultName = diff.getAttribute('result');
const filter = diff.closest('filter');
// Check for overdriven diffuseConstant (clips to white)
if (diffConst >= 3) {
// At diffuseConstant >= 3: all pixels with dot(N,L) >= 0.33 clip to 1.0
// On smooth text surfaces: majority of pixels exceed this threshold
findings.push({ severity: 'high', diff,
issue: `feDiffuseLighting diffuseConstant=${diffConst} — overdriven; all pixels with dot(N,L) >= ${(1/diffConst).toFixed(2)} clip to 1.0 (white); majority of consent text pixels washed to white when composited` });
}
// Check CSS-overridden lighting-color
const cssLightColor = getComputedStyle(diff).getPropertyValue('lighting-color') || '';
const attrLightColor = diff.getAttribute('lighting-color') || '';
if (cssLightColor && attrLightColor && cssLightColor !== attrLightColor) {
findings.push({ severity: 'medium', diff,
issue: `feDiffuseLighting CSS lighting-color="${cssLightColor}" overrides attribute "${attrLightColor}" — attribute audit finds neutral value; computed style reveals attack color` });
}
if (!resultName || !filter) continue;
// Check for diffuse output feeding feBlend screen (wash attack)
const blends = filter.querySelectorAll('feBlend');
for (const blend of blends) {
if ((blend.getAttribute('in') === resultName || blend.getAttribute('in2') === resultName)
&& blend.getAttribute('mode') === 'screen') {
// Simulate screen blend with representative diffuse value
const lightColor = cssLightColor || attrLightColor || '#ffffff';
const lightBrightness = lightColor === '#ffffff' ? 1.0 : 0.5;
const diffuseVal = Math.min(1, diffConst * 0.9 * lightBrightness);
// Simulate screen with dark text (normalized 0.106 = #1a1a1a)
const textPixel = 0.106;
const screenOut = 1 - (1 - diffuseVal) * (1 - textPixel);
const bgLum = 0.96; // typical white background
const contrast = bgLum / screenOut;
if (contrast < 3.0) {
findings.push({ severity: 'high', diff, blend,
issue: `feDiffuseLighting result="${resultName}" fed to feBlend mode="screen" — simulated contrast ${contrast.toFixed(2)}:1 on dark text (#1a1a1a); below 3:1 WCAG minimum` });
}
}
}
// Check feComposite arithmetic consuming diffuse (overdrive clips to white)
const composites = filter.querySelectorAll('feComposite');
for (const comp of composites) {
if ((comp.getAttribute('in') === resultName || comp.getAttribute('in2') === resultName)
&& comp.getAttribute('operator') === 'arithmetic') {
if (diffConst >= 2) {
findings.push({ severity: 'high', diff, comp,
issue: `feDiffuseLighting diffuseConstant=${diffConst} result fed to feComposite arithmetic — overdriven output (>1.0) clips to white in all illuminated regions of consent text` });
}
}
}
}
return findings.length ? findings : null;
}
Remediation
| Control | How it helps |
|---|---|
Flag feDiffuseLighting with diffuseConstant ≥ 2 on filters applied to consent text elements; at diffuseConstant=2, all pixels with dot(N,L) ≥ 0.5 produce output exceeding 1.0, which clips to white when composited via feComposite arithmetic — for typical smooth text height maps this covers the majority of the illuminated text surface area | diffuseConstant overdrive is a wide-area effect, unlike feSpecularLighting's narrow hotspot; at diffuseConstant=2–5, essentially all front-facing text pixels receive clipped-to-white illumination; the mathematical threshold (1/diffuseConstant) for clipping is directly computable from the attribute value without needing to simulate the full lighting calculation |
Check lighting-color via getComputedStyle rather than SVG attribute read; compare computed value against page background color and against threshold brightness (near-white values within 10% of background luminance are a higher-risk signal) | CSS lighting-color overrides the SVG attribute value via the standard CSS cascade; an attribute read returns the SVG value; getComputedStyle reflects the actual rendering value; comparing the computed lighting-color to the page background color identifies background-matching illumination attacks regardless of what the attribute reads |
| For feDiffuseLighting result used as feBlend input with mode="screen": simulate the screen blend formula using the computed diffuse output magnitude (diffuseConstant × lighting-color normalized brightness × 0.9 as a typical dot(N,L) estimate) applied to the consent element's fill color; flag if simulated output contrast falls below 3:1 | The screen blend bleach severity depends on the combined product of diffuseConstant, lighting-color brightness, and the Lambertian dot product; the dot product for front-facing surfaces at moderate elevation angles is typically 0.7–0.95; using 0.9 as a conservative estimate provides a realistic simulation of the majority-pixel output; this catches broad illumination attacks that a color-comparison audit misses |
| Check feDistantLight azimuth and elevation values on feDiffuseLighting applied to consent text: elevation values above 70° produce near-maximum dot(N,L) for all surface normals pointing in the +Z direction — which is the predominant normal direction for smooth text rendered from SourceAlpha; any elevation above 70° on a consent text filter with diffuseConstant ≥ 2 and near-white lighting-color is a high-severity finding | High elevation angles (nearly vertical light direction) illuminate all flat-surface pixels at near-maximum intensity; combined with overdrive diffuseConstant values, this means nearly the entire consent text area receives clipped-to-white illumination; the feDistantLight elevation is the parameter that makes diffuse overdrive a broad-area erasure attack rather than directional shadow and highlight |
SkillAudit checks feDiffuseLighting diffuseConstant against overdrive thresholds, reads lighting-color via computed style to detect CSS attribute overrides, simulates feBlend screen output brightness for diffuse contributions, and flags high-elevation feDistantLight configurations that maximize broad-area illumination of consent text. Run a free audit on any MCP server GitHub URL to detect diffuse lighting consent text manipulation and the full SVG filter consent rendering attack surface.