MCP server CSS feSpecularLighting security: extreme specularConstant/specularExponent feBlend screen glyph edge erasure, calibrated light direction hotspot alignment with consent clause words, specular output as displacement map source, and animated light position timing attack
Published 2026-09-26 — SkillAudit Research
The SVG feSpecularLighting filter primitive implements the specular component of the Phong lighting model. It takes a height-map input (derived from the input image's alpha channel — typically SourceAlpha or a blurred version of it) and computes the specular intensity at each pixel using: specularConstant × pow(dot(N, H), specularExponent), where N is the surface normal derived from the height map gradient and H is the normalized half-vector between the light direction and the viewer direction. The lighting-color property scales the specular output color. Light source types available are feDistantLight (directional, infinite distance), fePointLight (omnidirectional, finite position), and feSpotLight (cone with controllable angle and focus).
The Phong model's specular contribution is typically a localized bright highlight used for "shiny" effects on 3D-looking elements. For consent text manipulation, four vectors exploit the geometric and parametric controllability of the specular lighting model: (1) extreme parameter values create an arbitrarily narrow, arbitrarily bright highlight whose position and color can be controlled independently of the text's own appearance; (2) the light source direction parameters give precise positional control over where the highlight falls within the text element's bounding box; (3) the specular lighting output image can serve as a displacement field for feDisplacementMap; and (4) light source position animation enables timing-synchronized hotspot movement.
Precision control: Unlike blur or turbulence attacks that apply effects uniformly or randomly, feSpecularLighting with calibrated fePointLight coordinates can target a specific pixel region within the consent text bounding box. An attacker who knows the SVG viewport dimensions and the approximate word positions in the consent text can compute exact fePointLight x/y/z values to place the specular hotspot over the acceptance keyword ("agree", "accept", "terms") rather than neutral words — maximizing legal impact of the erasure.
Attack findings
The Phong specular exponent controls hotspot sharpness: high
specularExponent values (64–128) concentrate the specular contribution into a very narrow angular range, producing a very small, very bright highlight. The specularConstant multiplies this output — at value 20, the specular contribution exceeds the maximum channel value (1.0) everywhere in the hotspot, saturating to full intensity. When lighting-color is set to the page background color (e.g., #f5f5f5), the saturated highlight is a background-colored patch. A feBlend mode="screen" compositing this specular output over SourceGraphic applies the screen formula: 1 - (1-specular) × (1-source). At hotspot positions where specular≈1 (background color), screen output approaches 1 regardless of source — glyph edge pixels at hotspot positions are erased to background color.
<filter id="specular-erase">
<!-- feGaussianBlur: smooth height map for lighting normal computation -->
<feGaussianBlur in="SourceAlpha" stdDeviation="4" result="height-map"/>
<feSpecularLighting in="height-map"
specularConstant="20" specularExponent="128"
lighting-color="#f5f5f5"
result="spec-highlight">
<!-- fePointLight: position calibrated to consent acceptance clause -->
<fePointLight x="140" y="22" z="30"/>
</feSpecularLighting>
<!-- feBlend screen: specular (bright background-colored spot) over text -->
<!-- screen(bg-color, dark-text) → bg-color where specular is bright -->
<feBlend in="spec-highlight" in2="SourceGraphic" mode="screen"/>
</filter>
<!-- Phong model at hotspot:
dot(N, H) ≈ 1.0 (surface normal aligned with half-vector at hotspot center)
specularConstant × pow(1.0, 128) = 20 × 1.0 = 20 → clipped to 1.0
lighting-color = #f5f5f5 → specular output = #f5f5f5 (background color)
feBlend screen at hotspot glyph pixel (#1a1a1a ≈ 0.106):
screen(0.96, 0.106) = 1 - (1-0.96)(1-0.106) = 1 - 0.04 × 0.894 = 0.964
Output: ≈#f6f6f6 → background color → glyph edges at hotspot: INVISIBLE -->
The specular hotspot position within the filtered element's bounding box is determined by the light source type and its geometric parameters. For
fePointLight, the hotspot falls at the point on the height-map surface that produces the maximum dot(N, H) value, which is controlled by the x/y position of the light relative to the surface. An MCP server that knows the SVG element dimensions and approximate glyph layout positions can compute fePointLight x/y coordinates that place the specular hotspot over a specific word — for example, "agree" or "accept" in the consent text. The remaining words in the consent text are outside the hotspot and render normally; only the acceptance-critical word is erased. This selective approach makes the attack harder to detect by statistical contrast measurement: most words are readable and pass contrast checks; only one or two critical words are targeted.
<!-- Consent text: "I agree to the Terms of Service" -->
<!-- Word "agree" at approximately x=15-60px within text element bounding box -->
<filter id="word-target-specular">
<feGaussianBlur in="SourceAlpha" stdDeviation="3" result="hmap"/>
<feSpecularLighting in="hmap"
specularConstant="15" specularExponent="64"
lighting-color="#ffffff"
result="targeted-spec">
<!-- fePointLight: x=37 places hotspot over "agree" word center -->
<!-- Attacker computed: text x offset + "I " width + "agree" center = ~37px -->
<fePointLight x="37" y="12" z="20"/>
</feSpecularLighting>
<feBlend in="targeted-spec" in2="SourceGraphic" mode="screen"/>
</filter>
<!-- Hotspot coverage:
specularExponent=64 → hotspot radius ≈ 20px (vs element width 280px)
Only pixels within ~20px of (37, 12) receive strong specular contribution
"I " (0-14px): no specular → renders normally → READABLE
"agree" (15-60px): inside hotspot → specular erases glyph edges → ILLEGIBLE
"to the Terms of Service" (61-280px): outside hotspot → READABLE
Contrast audit: measures aggregate contrast over 280px width
Most pixels unaffected → aggregate contrast passes → attack missed -->
The
feSpecularLighting output image contains per-pixel intensity values in the R, G, B channels representing the specular contribution. When used as the in2 displacement field for a feDisplacementMap, the specular intensity values control per-pixel displacement: pixels in the specular hotspot receive a different displacement magnitude and direction than pixels outside the hotspot. This produces a spatially non-uniform displacement pattern keyed to the lighting geometry rather than a simple translation or random field. For consent text, the displacement varies across the character positions — some glyphs are shifted more than others depending on their position relative to the light source. The irregular displacement pattern can disrupt letter spacing and character recognition without producing the obvious visual noise of a turbulence-based displacement. An auditor checking feDisplacementMap scale may not flag the specular input as equivalent in effect to a feTurbulence input.
<filter id="specular-displace">
<feGaussianBlur in="SourceAlpha" stdDeviation="5" result="height"/>
<!-- Specular output: per-pixel intensity keyed to light geometry -->
<feSpecularLighting in="height" specularConstant="8" specularExponent="32"
result="spec-displacement-field">
<feDistantLight azimuth="45" elevation="30"/>
</feSpecularLighting>
<!-- feDisplacementMap: use specular values as displacement field -->
<!-- xChannelSelector="R", yChannelSelector="G": specular RGB drives x,y displacement -->
<feDisplacementMap in="SourceGraphic" in2="spec-displacement-field"
scale="18" xChannelSelector="R" yChannelSelector="G"/>
</filter>
<!-- Displacement varies spatially with lighting geometry:
Near light direction peak: specular R≈1, G≈1 → max displacement
Away from peak: specular R≈0, G≈0 → no displacement
Result: glyphs near light direction shifted 18px; other glyphs unchanged
Character spacing disrupted in hotspot region → word recognition impaired
feDisplacementMap audit: checks scale (18) but not input source type -->
The child light source element of feSpecularLighting (
fePointLight, feSpotLight, or feDistantLight) accepts animated x, y, z attributes. An MCP server can initialize the light source at a neutral position (e.g., outside the consent text bounding box at x=-200) and animate it to move over the acceptance clause words at the button activation time. At page load: no specular hotspot on text → text renders normally → static audit passes. At button activation: light position animated to x=37 (over "agree" word) → specular hotspot erases target word's glyph edges. The animation can use fill="freeze" to hold the final position after the animation completes — preserving the erased state at click time.
<filter id="animated-specular">
<feGaussianBlur in="SourceAlpha" stdDeviation="3" result="hmap"/>
<feSpecularLighting in="hmap" specularConstant="18" specularExponent="96"
lighting-color="#f8f8f8" result="anim-spec">
<fePointLight z="25">
<!-- x: light starts far left (neutral), moves to "agree" at t=4s -->
<animate attributeName="x" from="-200" to="37"
dur="4s" begin="0s" fill="freeze"/>
<!-- y: fixed at consent text vertical center -->
<animate attributeName="y" from="12" to="12" dur="4s" fill="freeze"/>
</fePointLight>
</feSpecularLighting>
<feBlend in="anim-spec" in2="SourceGraphic" mode="screen"/>
</filter>
<!-- Timeline:
t=0s: fePointLight x=-200 → hotspot outside element → text renders normally
t=4s: fePointLight x=37 → hotspot over "agree" → specular erases glyph edges
fill="freeze": hotspot stays at x=37 after animation → glyph edges remain erased
button.disabled = false at t=4s → user can click when "agree" is erased
Static audit at DOMContentLoaded: fePointLight x=-200 → no text impact → PASS
Dynamic audit at t=4s: fePointLight x=37 → hotspot on text → FAIL -->
Detection
function checkFeSpecularLighting(svgRoot) {
const findings = [];
const specEls = svgRoot.querySelectorAll('feSpecularLighting');
for (const spec of specEls) {
const specConst = parseFloat(spec.getAttribute('specularConstant') || '1');
const specExp = parseFloat(spec.getAttribute('specularExponent') || '1');
const resultName = spec.getAttribute('result');
const filter = spec.closest('filter');
// Check for extreme parameters (narrow bright hotspot)
if (specConst >= 10 && specExp >= 64) {
// Check if lighting-color matches background
const lightColor = getComputedStyle(spec).getPropertyValue('lighting-color')
|| spec.getAttribute('lighting-color') || '';
findings.push({ severity: 'high', spec,
issue: `feSpecularLighting specularConstant=${specConst} specularExponent=${specExp} — extreme params create saturated narrow hotspot; if lighting-color="${lightColor}" matches background, feBlend screen will erase glyph edges at hotspot` });
}
// Check for animated light source position
const lightSrc = spec.querySelector('fePointLight, feSpotLight, feDistantLight');
if (lightSrc) {
const xAnim = lightSrc.querySelector('animate[attributeName="x"], animate[attributeName="azimuth"]');
if (xAnim) {
findings.push({ severity: 'medium', spec, lightSrc,
issue: `feSpecularLighting light source position animates — hotspot moves from initial position to final position; static audit sees initial (potentially neutral) position only` });
}
}
if (!resultName || !filter) continue;
// Check if specular output feeds feDisplacementMap
const dispMaps = filter.querySelectorAll('feDisplacementMap');
for (const dm of dispMaps) {
if (dm.getAttribute('in2') === resultName) {
const scale = parseFloat(dm.getAttribute('scale') || '0');
if (scale >= 8) {
findings.push({ severity: 'medium', spec, dm,
issue: `feSpecularLighting result="${resultName}" used as feDisplacementMap in2 scale=${scale} — specular intensity drives spatially-variable glyph displacement` });
}
}
}
// Check feBlend screen on specular output
const blends = filter.querySelectorAll('feBlend');
for (const blend of blends) {
if (blend.getAttribute('in') === resultName && blend.getAttribute('mode') === 'screen') {
findings.push({ severity: 'high', spec, blend,
issue: `feSpecularLighting result="${resultName}" fed to feBlend mode="screen" over SourceGraphic — specular hotspot screen-blends background-matching color over consent text glyph edges` });
}
}
}
return findings.length ? findings : null;
}
Remediation
| Control | How it helps |
|---|---|
Flag feSpecularLighting with specularConstant ≥ 5 or specularExponent ≥ 32 on filters applied to consent text; check whether lighting-color matches the background color via computed style comparison | High specularConstant saturates the hotspot to maximum intensity (clipped to 1.0); high specularExponent concentrates that intensity into a narrow angular cone; together they create a region of near-maximum background-colored pixels that feBlend screen composites over text, erasing glyph edges — the two parameters together (not individually) define the attack severity |
| For feSpecularLighting on consent text filters: compute the specular hotspot position from the child light source element (fePointLight x/y or feDistantLight azimuth/elevation) and check whether it overlaps glyph bounding boxes; flag filters where hotspot position is within the consent element's bounding box | Word-level targeting requires precise light source positioning; computing the hotspot position from light parameters and comparing to word bounding boxes reveals whether specific consent words are targeted rather than applying a uniform effect; this distinguishes decorative specular from targeted erasure |
Re-audit feSpecularLighting light source positions after all SVG animate elements complete; check for fill="freeze" animations on fePointLight/feSpotLight x/y attributes that move the light source from outside to inside the consent text bounding box | Animated light position starts outside the consent text area (neutral, no visual effect) and animates to the target word position at button activation time; only sampling the final animated state reveals whether the terminal light position places the hotspot over consent text |
| When feSpecularLighting output feeds feDisplacementMap, evaluate the effective displacement magnitude using the specular output range: at specularConstant ≥ 5, the hotspot region has near-maximum channel values; scale × 1.0 = the maximum pixel shift in the hotspot; flag if this exceeds glyph stroke width | Specular-driven displacement is harder to detect than turbulence-driven because auditors familiar with feDisplacementMap attacks expect feTurbulence as the in2 source; the specular input is less commonly checked; the displacement magnitude in the hotspot region equals scale × (specular intensity at peak), not a uniform value — only the hotspot region is significantly displaced |
SkillAudit checks feSpecularLighting parameters against hotspot saturation thresholds, computes hotspot position from child light source geometry and compares to consent text word bounding boxes, traces specular result chains through feBlend and feDisplacementMap, and evaluates animated light source terminal positions. Run a free audit on any MCP server GitHub URL to detect specular lighting consent text manipulation and the full SVG filter consent rendering attack surface.