MCP server CSS feImage security: external resource alpha mask erasure, CORS-disabled zero-alpha collapse, base64 transparent image injection, and SVG fragment white-rectangle overlay of consent text

Published 2026-09-30 — SkillAudit Research

The SVG feImage filter primitive loads an image resource into the filter graph as a new image input. The image source is specified by xlink:href (or href) and can be an external URL, a data: URI, or a reference to an SVG fragment in the same document. Unlike primitives such as feFlood that generate images programmatically from CSS property values, feImage introduces an external resource — one that may not be inspectable by DOM property checks — as a filter input. This creates a distinct class of consent text attack vectors where the erase mechanism lives in an external resource rather than in the filter markup itself.

The four vectors below operate at the intersection of the filter graph and the resource-loading model: transparent PNG alpha masks, CORS-failure zero-alpha collapse, base64 opaque encoding, and SVG fragment foreground composition. See also the related feComposite security page for the operator context and the DOM Ghost Attack post for the "in" operator erasure mechanism these vectors exploit.

External resource attack surface: feImage moves part of the attack into a network resource that the MCP server controls. The filter markup in the DOM looks structurally benign — a feImage element with a URL — but the pixel content of the image determines the attack effect. An audit that inspects only the filter DOM cannot determine the image's alpha channel without fetching and decoding the resource.

Attack findings

CRITICAL
Transparent PNG as feComposite in2 erases all consent text pixels via operator="in"
A feImage element loads a 1×1 or full-size transparent PNG (all pixels RGBA 0,0,0,0) and names its output result (e.g., result="mask"). A subsequent feComposite in="SourceGraphic" in2="mask" operator="in" clips the consent text to the image's alpha channel. Because the image alpha is zero everywhere, the output is fully transparent. All DOM checks pass: the element has positive bounding rect, non-transparent fill, display:block, visibility:visible. The filter markup contains no explicit zero-opacity value — the zero comes from the image pixels. Detection requires fetching the feImage resource and checking its decoded alpha channel.
<filter id="f1">
  <feImage xlink:href="https://cdn.example.com/transparent.png" result="mask"
           x="0" y="0" width="100%" height="100%"/>
  <feComposite in="SourceGraphic" in2="mask" operator="in"/>
</filter>
CRITICAL
CORS-disabled cross-origin image load produces zero-alpha feImage output
When a feImage references a cross-origin URL that returns an image without CORS headers (Access-Control-Allow-Origin absent), the browser's tainted canvas rules prevent the image pixels from being used in the compositing pipeline. The SVG filter specification requires that a CORS-unavailable image be treated as a fully transparent image in the filter graph. The MCP server hosts the image without CORS headers. From the same origin, the image is accessible; from a user's browser origin, it is CORS-blocked. The filter graph receives a zero-alpha image. Combined with operator="in", this erases the consent text. From within the MCP server's own test infrastructure (same origin), the attack is invisible — it manifests only to users.
HIGH
Base64-encoded transparent image in data URI avoids URL-inspection detection
A transparent 1×1 PNG encoded as a data: URI contains approximately 68 base64 characters and is syntactically opaque to string-inspection audits that check href attribute values for suspicious URL patterns. The data URI does not resolve over the network, avoiding content-security-policy URL checks and network-request monitoring. The decoded image is fully transparent (or contains a white-on-white rectangle for the "over" attack variant). Detection requires base64-decoding the data URI, identifying the image format, and checking the pixel alpha channel.
<!-- 1x1 transparent PNG in data URI -->
<feImage xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" result="mask"/>
<feComposite in="SourceGraphic" in2="mask" operator="in"/>
HIGH
feImage loading SVG fragment white rectangle as feComposite foreground covers consent text
feImage can reference an SVG element within the same document via a fragment identifier (xlink:href="#white-rect"). An <svg> element with a white <rect>, hidden off-screen or in a <defs> section, becomes the image source. This white rectangle is used as the in= (foreground) operand of feComposite operator="over", painting a white rectangle over the consent text. The SVG fragment is part of the document DOM but is typically invisible by position or transform; the connection to the filter is not apparent without resolving the feImage href to its referenced element and reading its rendered content.

Detection algorithm

async function detectFeImageAttacks(consentEl) {
  const style = getComputedStyle(consentEl);
  const filterVal = style.filter;
  if (!filterVal || filterVal === 'none') return null;

  const filterId = filterVal.match(/url\(["']?#([^"')]+)["']?\)/)?.[1];
  if (!filterId) return null;

  const svgRoot = consentEl.closest('svg') || document;
  const filter = svgRoot.querySelector(`filter#${filterId}`);
  if (!filter) return null;

  const primitives = Array.from(filter.querySelectorAll('*'));
  const findings = [];

  for (const prim of primitives) {
    if (prim.tagName !== 'feImage') continue;
    const href = prim.getAttribute('xlink:href') || prim.getAttribute('href') || '';
    const resultName = prim.getAttribute('result');

    // Check if this feImage result feeds operator="in" as in2
    const downstream = primitives.filter(p =>
      p.tagName === 'feComposite' &&
      p.getAttribute('in2') === resultName &&
      p.getAttribute('operator') === 'in'
    );
    if (downstream.length === 0) continue; // not used as zero-alpha mask

    if (href.startsWith('data:')) {
      // Decode base64 data URI and check alpha channel
      const transparent = await checkDataUriTransparency(href);
      if (transparent) {
        findings.push({ severity: 'critical', prim,
          issue: `feImage data URI decodes to transparent image used as in2 of operator="in" — consent text fully erased` });
      }
    } else if (href.startsWith('#')) {
      // SVG fragment reference
      findings.push({ severity: 'medium', prim,
        issue: `feImage href="${href}" references local SVG fragment as in2 of operator="in" — verify fragment pixel content` });
    } else if (href) {
      // External URL — flag for cross-origin CORS risk and transparency check
      findings.push({ severity: 'critical', prim,
        issue: `feImage href="${href}" loads external resource as in2 of operator="in" — CORS-blocked images produce zero-alpha; fetch and check decoded alpha` });
    }
  }

  return findings.length ? findings : null;
}

async function checkDataUriTransparency(dataUri) {
  return new Promise(resolve => {
    const img = new Image();
    img.onload = () => {
      const c = document.createElement('canvas');
      c.width = img.width; c.height = img.height;
      const ctx = c.getContext('2d');
      ctx.drawImage(img, 0, 0);
      const data = ctx.getImageData(0, 0, c.width, c.height).data;
      // Check if all alpha values are below threshold
      let maxAlpha = 0;
      for (let i = 3; i < data.length; i += 4) maxAlpha = Math.max(maxAlpha, data[i]);
      resolve(maxAlpha < 10);
    };
    img.src = dataUri;
  });
}

Remediation

ControlHow it helps
For every feImage element in a consent text element's filter graph that feeds as in2 into feComposite operator="in", fetch the referenced image resource (or decode the data URI), render it to a canvas, and check the decoded alpha channel for near-zero values across the filter region Transparent PNG and base64 data URI variants — the zero-alpha source is in the image pixels, not the filter markup; only pixel-level inspection of the decoded image reveals the attack
Flag any feImage referencing a cross-origin URL without CORS headers as a CORS-risk zero-alpha source — verify that the image origin includes Access-Control-Allow-Origin: * or an appropriate origin header that covers the user's browser origin CORS-disabled cross-origin variant — the attack only manifests to users from other origins; same-origin testing misses it; CORS header verification is required
Resolve feImage href="#fragment" references to their target SVG elements, read the fragment's rendered visual content (fill, rect geometry), and check whether the fragment produces a white or background-matching rectangle that would cover consent text as a foreground composite SVG fragment white-rectangle variant — the attack source is a DOM element outside the filter definition; only following the fragment reference reveals the composition target

SkillAudit resolves feImage href references, fetches external images with CORS checking, decodes data URIs to pixel arrays, and checks the alpha channel of images used as in2 of operator="in" composites on consent text elements. Run a free audit on any MCP server GitHub URL to detect feImage alpha mask attacks and the full SVG filter consent attack surface.