MCP server CSS lighting-color security: SVG feDiffuseLighting background wash, feSpecularLighting blowout, CSS attribute override, and animated timing attack on consent text

Published 2026-09-26 — SkillAudit Research

The CSS lighting-color property controls the color of the simulated light source used by two SVG filter primitives: feDiffuseLighting and feSpecularLighting. These primitives model 3D lighting effects applied to SVG element surfaces — they compute a height map from an input (typically a feGaussianBlur of the element's alpha channel) and compute a diffuse or specular reflection based on a virtual light source position.

The rendered output of feDiffuseLighting is a per-pixel light intensity map multiplied by lighting-color. When this map is composited with the original text element via feComposite operator="arithmetic" or feBlend mode="screen", the lighting color tints the text. By setting lighting-color to the page background color with high diffuse or specular constants, an MCP server can wash consent text to the background color from the inside out — exploiting the lighting math rather than a simple overlay. CSS lighting-color can override the SVG attribute, making this invisible to attribute-reading auditors.

Lighting primitive audit complexity: Detecting lighting-color attacks requires understanding the lighting filter graph, the compositing mode used to apply the lighting result, and the effective contribution of lighting-color to the final pixel values. Unlike feFlood attacks (which replace pixels with a flood), lighting attacks tint pixels — the rendered color is a mathematical combination of the source pixel and the lighting result. The attack surface is the same (invisible consent text) but the detection path requires simulating the lighting math or measuring composited contrast.

Attack findings

HIGH
feDiffuseLighting with background-matching lighting-color and high diffuseconstant — consent text washed to background color via diffuse lighting compositing
A feDiffuseLighting primitive uses a feGaussianBlur of the consent text's alpha channel as its height map. The simulated light source is a point light at a low elevation angle. lighting-color is set to the page background color (#ffffff). diffuseConstant is set to 3.0 — the diffuse lighting contribution is 3× the normal surface response. The lighting result is blended with the source text using feComposite operator="arithmetic", which mathematically replaces text pixels with a weighted sum biased toward the background-colored lighting output. Consent text glyphs effectively disappear into the background.
<filter id="consent-light">
  <!-- Blur alpha channel for height map -->
  <feGaussianBlur stdDeviation="2" in="SourceAlpha" result="blur"/>
  <!-- Diffuse lighting with background-matching color -->
  <feDiffuseLighting lighting-color="#ffffff" diffuseConstant="3.0"
      surfaceScale="5" result="light" in="blur">
    <fePointLight x="100" y="50" z="30"/>
  </feDiffuseLighting>
  <!-- Arithmetic composite: k1*i1*i2 + k2*i1 + k3*i2 + k4 -->
  <!-- With k1=0, k2=0, k3=1, k4=0: result = light (source ignored) -->
  <feComposite in="light" in2="SourceGraphic" operator="arithmetic"
      k1="0" k2="0" k3="1" k4="0"/>
</filter>

/* Effect: SourceGraphic (dark text) discarded; lighting output (white) used
   Consent text replaced by background-colored lighting result
   lighting-color="#ffffff" → background color → text disappears
   Detection: read lighting-color via getComputedStyle; check compositing formula */
HIGH
CSS lighting-color overrides SVG attribute — attribute shows neutral gray; computed value reveals background-matching white
A feDiffuseLighting element has lighting-color="#808080" as an SVG attribute (neutral gray — a reasonable lighting color for legitimate effects). A CSS rule overrides this: #consent-filter feDiffuseLighting { lighting-color: #ffffff; }. The rendered lighting effect now uses white as the light color — matching the page background. An auditor reading feDiffuseLighting.getAttribute('lighting-color') finds "#808080" and proceeds without concern. Only getComputedStyle(feDiffuseLighting).lightingColor returns "rgb(255,255,255)", revealing the background-matching override.
<!-- SVG attribute: neutral gray lighting (innocuous) -->
<feDiffuseLighting lighting-color="#808080" diffuseConstant="1.5">
  <fePointLight x="150" y="60" z="20"/>
</feDiffuseLighting>

/* CSS override: background-matching white */
#consent-filter feDiffuseLighting {
  lighting-color: #ffffff;    /* overrides "#808080" → now background-matching */
}

/* getAttribute('lighting-color'):               "#808080" → gray → PASS (incorrect)
   getComputedStyle(feDiffuse).lightingColor:    "rgb(255,255,255)" → white → ATTACK DETECTED
   CSS author stylesheet wins over SVG presentation attribute in cascade */
MEDIUM
feSpecularLighting blowout — specular highlight in background color erases text structure at glyph highlight positions
feSpecularLighting adds a specular highlight at positions on the surface where the normal vector faces the light source — typically the raised edges of glyphs in a bump-map effect. An MCP server sets specularConstant to a high value (10–20) and lighting-color to the page background color. The specular highlight output is blended with the source text using feBlend mode="screen" or feComposite operator="arithmetic". Glyph edges — the most visually distinctive parts of letterforms — are washed with an intense background-colored highlight, reducing contrast at exactly the positions needed to recognize letterforms. The text "exists" in the DOM and has non-zero dimensions, but glyph recognition is severely impaired.
<filter id="consent-specular">
  <feGaussianBlur stdDeviation="1" in="SourceAlpha" result="blur"/>
  <feSpecularLighting lighting-color="#ffffff" specularConstant="15"
      specularExponent="30" surfaceScale="4" result="specular" in="blur">
    <fePointLight x="50" y="-100" z="200"/>    <!-- overhead light -->
  </feSpecularLighting>
  <!-- Screen blend: erases dark pixels where specular highlight is bright -->
  <feBlend in="specular" in2="SourceGraphic" mode="screen"/>
</filter>

/* feBlend mode=screen formula: 1 - (1 - src1) * (1 - src2)
   specular=white(1.0) → 1 - (1-1.0)*(1-src2) = 1.0 for all src2
   Glyph edges become pure white regardless of source pixel color
   Letter structure erased at highlight positions
   Detection: check specularConstant * lightingColor brightness; flag > threshold */
MEDIUM
Animated lighting-color timing attack — transitions from visible tint to background-matching wash at button activation
A CSS animation transitions lighting-color from a visible neutral color (light gray or the text color) to the page background color over the button activation delay. At page load, the lighting effect is a neutral tint that does not obscure the text. At button activation time, the lighting color matches the background — the lighting output washes the text away. This pattern is the lighting-layer variant of the dashoffset timing attack and the flood-opacity timing attack.
@keyframes lighting-reveal {
  0%   { lighting-color: #333333; }    /* page load: dark tint, text visible */
  90%  { lighting-color: #cccccc; }    /* mostly harmless gray tint */
  100% { lighting-color: #ffffff; }    /* button activation: background wash */
}

#consent-filter feDiffuseLighting {
  animation: lighting-reveal 3.5s forwards;    /* matches button delay */
  diffuseConstant: 2.5;
}

/* Static scan at t=0: lighting-color=#333333 (dark) → no background match → PASS
   At t=3.5s: lighting-color=#ffffff → background wash → text invisible
   Detection: simulate animation to button delay; check lighting-color vs background */

Detection

function checkLightingColor(svgRoot, pageBackground = '#ffffff') {
  const findings = [];

  const textEls = svgRoot.querySelectorAll('text, tspan, [data-consent]');
  for (const el of textEls) {
    const filterRef = el.getAttribute('filter') || getComputedStyle(el).filter;
    if (!filterRef || filterRef === 'none') continue;

    const filterId = (filterRef.match(/url\(#([^)]+)\)/) || [])[1];
    if (!filterId) continue;

    const filter = svgRoot.getElementById(filterId);
    if (!filter) continue;

    const lightingPrimitives = filter.querySelectorAll('feDiffuseLighting, feSpecularLighting');
    for (const primitive of lightingPrimitives) {
      const cs = getComputedStyle(primitive);

      /* Check CSS vs attribute override */
      const cssColor  = cs.lightingColor;
      const attrColor = primitive.getAttribute('lighting-color') || '';
      if (attrColor && cssColor && normalizeColor(attrColor) !== normalizeColor(cssColor)) {
        findings.push({
          severity: 'high', primitive,
          issue: `lighting-color attribute "${attrColor}" overridden by CSS "${cssColor}" — attribute audit misses CSS override`
        });
      }

      /* Check for background-matching lighting color */
      const bgRGB      = parseColorToRGB(pageBackground);
      const lightRGB   = parseColorToRGB(cssColor || attrColor || 'white');
      const brightness = (lightRGB.r * 0.299 + lightRGB.g * 0.587 + lightRGB.b * 0.114) / 255;

      /* High-brightness light color in a diffuse/specular context → potential wash */
      if (brightness > 0.85) {
        const diffuseConst   = parseFloat(primitive.getAttribute('diffuseConstant')   || '1');
        const specularConst  = parseFloat(primitive.getAttribute('specularConstant')  || '1');
        const effectConstant = Math.max(diffuseConst, specularConst);

        if (effectConstant > 1.5) {
          findings.push({
            severity: 'medium', primitive,
            issue: `${primitive.tagName} lighting-color="${cssColor}" (brightness=${brightness.toFixed(2)}) with ${primitive.tagName === 'feDiffuseLighting' ? 'diffuseConstant' : 'specularConstant'}=${effectConstant} — high-brightness light at amplified constant may wash consent text`
          });
        }
      }
    }
  }

  return findings.length ? findings : null;
}

Remediation

ControlHow it helps
Include feDiffuseLighting and feSpecularLighting in SVG filter graph traversal for consent text elementsLighting primitives can tint consent text to the background color — they are not purely decorative and must be audited when applied to text elements
Read lighting-color via getComputedStyle(primitive).lightingColor, not via getAttribute('lighting-color')CSS cascade overrides SVG presentation attributes; attribute-reading misses CSS-injected lighting color overrides
Flag high-brightness lighting-color values (luminance > 0.85) when combined with diffuseConstant > 1.5 or specularConstant > 5High-intensity background-colored lighting can wash text to background color; the combination of high-brightness color and amplified constant is the attack signature
Simulate CSS animation timelines on lighting primitives to the button activation delay timeTiming attacks use lighting-color animations that appear neutral at load time but become background-matching at button activation

SkillAudit traverses SVG filter graphs on consent text elements and checks feDiffuseLighting and feSpecularLighting primitives for background-matching lighting colors, CSS cascade overrides, and high specular/diffuse constants that amplify the wash effect. Run a free audit on any MCP server GitHub URL to detect lighting-color attacks and the full SVG filter consent manipulation surface.