← Blog

MCP server SVG feSpecularLighting specularConstant & specularExponent consent security

Four SA-CSS-KSP consent attack patterns using feSpecularLighting specularConstant (ks) and specularExponent — extreme ks amplifying reflection to white-out, near-zero exponent collapsing the specular lobe to full-surface coverage, high exponent with grazing light producing dark-out, and JS mousedown ks injection for click-time attacks.

By SkillAudit  |  23 Oct 2026

The feSpecularLighting filter primitive renders specular reflection using the Phong model. The output intensity at each pixel is: I_s = ks × (N·H)^exp, where N is the surface normal derived from the alpha channel (via the surfaceScale bump map), H is the half-vector between the light direction and the view direction, ks is the specularConstant attribute (reflection amplitude), and exp is the specularExponent attribute (angular width of the highlight). The output is then scaled by the light color. The result is blended with the source element — typically via feComposite or feBlend — to create a lighting effect. specularConstant and specularExponent are orthogonal attack vectors: ks controls amplitude (how bright the highlight is), while exponent controls angular spread (how wide the highlight area is).

Amplitude-spread decomposition: ks and specularExponent provide two independent paths to full-surface white-out. Extreme ks multiplies even small (N·H) values into white. Near-zero exponent makes (N·H)^exp ≈ 1 for all normals, spreading the highlight over the entire surface. Both produce full-surface white-out; they differ only in which Phong parameter drives the attack. Scanners that only check one attribute may miss attacks via the other.

SA-CSS-KSP-001: specularConstant=100 white-out with overhead light

With specularConstant="100" and an overhead feDistantLight (elevation=90), the half-vector H equals the surface normal N for a flat surface — so (N·H) = 1.0 at every pixel. The output is I_s = 100 × 1.0^exp = 100, which clamps to 1.0 (maximum brightness) after SVG channel clamping. The feSpecularLighting output is a solid white layer covering the entire filter region. When this white layer is composited over the source element via feBlend mode="screen" (screen(A, white) = white), the consent element renders as a solid white rectangle. The consent text DOM, textContent, BCR, and getComputedStyle remain unchanged.

<filter id="ksp1">
  <feSpecularLighting surfaceScale="1"
    specularConstant="100"
    specularExponent="10"
    result="spec">
    <feDistantLight azimuth="90" elevation="90"/>
  </feSpecularLighting>
  <feBlend in="SourceGraphic" in2="spec" mode="screen"/>
</filter>
<!-- ks=100 × (N·H=1)^10 = 100 → clamped to 1 → white; screen(source, white)=white-out -->

SA-CSS-KSP-002: specularExponent=0.001 — flat specular covering entire surface

As specularExponent approaches 0, (N·H)^exp → 1 for all surface normals — including edge-on normals where the half-vector is nearly perpendicular to the normal. The physical interpretation: an exponent near 0 produces a perfectly diffuse specular distribution; the highlight spreads over the entire hemisphere. For specularExponent="0.001" and any light direction, the specular output approximates ks × 1.0 × L_color at every pixel — a flat constant proportional to ks and light brightness. With ks=1 and a white light source, the entire consent element renders with near-maximum specular brightness. Combined with feBlend screen, this produces white-out regardless of the light direction — the attack does not require a specific azimuth or elevation, making it light-direction-agnostic and harder to detect via light parameter analysis.

<filter id="ksp2">
  <feSpecularLighting surfaceScale="1"
    specularConstant="1"
    specularExponent="0.001"
    result="spec">
    <feDistantLight azimuth="0" elevation="45"/> <!-- any direction works -->
  </feSpecularLighting>
  <feBlend in="SourceGraphic" in2="spec" mode="screen"/>
</filter>
<!-- exp=0.001: (N·H)^0.001 ≈ 1 for all N; full-surface specular; direction-agnostic white-out -->

Direction-agnostic evasion: SA-CSS-KSP-002 produces white-out regardless of feDistantLight azimuth and elevation. Scanners that only flag specific light directions (e.g., elevation=0 dark-out path) miss this attack. Detection requires checking the specularExponent value directly.

SA-CSS-KSP-003: specularExponent=200 + grazing light — dark-out

At specularExponent="200", the specular lobe is extremely narrow — a near-perfect mirror. With a grazing feDistantLight (azimuth=0, elevation=5), the light comes nearly horizontal. The half-vector H is tilted far from the surface normal N (which is approximately vertical for a flat surface). The dot product (N·H) at most pixels ≈ cos(87.5°) ≈ 0.044. Raising this to the 200th power: 0.044^200 ≈ 10^{-273} ≈ 0. The feSpecularLighting output is near-zero (black) at virtually all pixels. When this near-black is used with feBlend mode="multiply" (multiply(source, 0)=0), the consent element renders as a solid black box. On a dark background, the contrast is 1:1; on a white background, it presents as a redacted bar.

<filter id="ksp3">
  <feSpecularLighting surfaceScale="1"
    specularConstant="5"
    specularExponent="200"
    result="spec">
    <feDistantLight azimuth="0" elevation="5"/> <!-- grazing horizontal light -->
  </feSpecularLighting>
  <feBlend in="SourceGraphic" in2="spec" mode="multiply"/>
</filter>
<!-- exp=200 + grazing: (N·H)^200 ≈ 0; spec≈black; multiply(source, 0)=0 → dark-out -->

SA-CSS-KSP-004: JS mousedown specularConstant injection (1 → 100)

Initially, specularConstant="1" with an overhead light produces a moderate specular highlight only at the exact center-overhead pixel (a small bright point). The consent text is readable around the highlight. On mousedown, the script changes specularConstant to "100". The 100× amplification instantly converts the moderate point highlight to a full-surface white-out. The transition from a small decorative highlight (ks=1) to a white panel (ks=100) is abrupt and instantaneous, occurring at the exact moment the user's click begins.

consentEl.addEventListener("mousedown", () => {
  document.querySelector("#consentFilter feSpecularLighting")
    .setAttribute("specularConstant", "100"); // amplify to white-out
});

Attack summary

IDspecularConstantspecularExponentLightEffectSeverity
SA-CSS-KSP-00110010Overhead elevation=90Full-surface white-outCritical
SA-CSS-KSP-00210.001Any directionFlat highlight white-out; direction-agnosticCritical
SA-CSS-KSP-0035200Grazing elevation=5Near-zero specular; multiply dark-outHigh
SA-CSS-KSP-0041→100 (mousedown)10OverheadWhite-out at click timeCritical

Defences

SkillAudit findings for this attack surface

CRITICAL SA-CSS-KSP-001: feSpecularLighting specularConstant="100" + overhead feDistantLight + feBlend screen — ks=100 amplifies (N·H=1.0) to 100; clamped to 1.0 (white); feBlend screen(source, white)=white; consent element renders as solid white rectangle; DOM unchanged; getComputedStyle.color unchanged.
CRITICAL SA-CSS-KSP-002: feSpecularLighting specularExponent="0.001" + any light + feBlend screen — exponent→0 causes (N·H)^exp→1 for all surface normals; flat full-surface highlight regardless of light direction; white-out independent of azimuth/elevation; evades light-direction-specific detectors; requires specularExponent value check.
HIGH SA-CSS-KSP-003: feSpecularLighting specularExponent="200" + grazing feDistantLight (elevation≈5) + feBlend multiply — narrow lobe with off-axis light produces near-zero specular output at all pixels; multiply(source, 0)=0 → dark-out; black rectangle on any background; alpha=1 so transparency checks pass.
CRITICAL SA-CSS-KSP-004: JS mousedown changes feSpecularLighting specularConstant from "1" to "100" — initial state moderate decorative highlight; white-out amplification at mousedown; MutationObserver on specularConstant attribute of feSpecularLighting elements in consent ancestor filters required.

Related: feSpecularLighting general consent attacks  |  feSpecularLighting surfaceScale attacks  |  feDistantLight elevation attacks  |  feGaussianBlur stdDeviation attacks

← Blog  |  Security Checklist