← Blog

MCP server SVG feImage href consent security

Four SA-CSS-FIH consent attack patterns using feImage href to inject an external or inline white-fill image as a filter input — composited over consent text to produce white-out — with the attack in the filter primitive chain, not the consent element's own src.

By SkillAudit  |  23 Oct 2026

feImage is an SVG filter primitive that loads an image from an href attribute and makes it available as a filter input (via the result attribute) for downstream primitives. Unlike a standard <image> element, feImage is inside a <filter> and is never directly rendered — it produces a named intermediate result in the filter pipeline. Its href accepts any URI that the browser can resolve, including data: URIs containing embedded SVG.

Scanner blind spot: Security scanners that check consent elements for suspicious src, href, or background-image attributes inspect the element's attributes. feImage href is on a child of a <filter> element — a different part of the DOM tree — and is typically not included in element-level attribute checks. The attack is inside the filter, invisible to element-level inspection.

SA-CSS-FIH-001: data: URI white SVG as feImage source

A feImage primitive loads a data: URI containing a solid-white SVG rectangle. The result is a white image the same size as the filter region. This image is then composited over the consent text using feBlend mode="screen" (or feComposite operator="over" with source set to the white image). The screen blend mode formula is 1 - (1-A)(1-B); compositing any non-black value with white always yields white. The consent text's rendered pixels are replaced with white. On a white background, the text is invisible.

<filter id="fih1">
  <feImage href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><rect width='100%25' height='100%25' fill='white'/></svg>"
    result="white_layer"/>
  <feBlend in="SourceGraphic" in2="white_layer" mode="screen"/>
</filter>
<!-- screen(any, white) = white; consent text overridden to white on all backgrounds -->

SA-CSS-FIH-002: same-origin URL feImage (asynchronous load)

Instead of a data: URI, feImage href points to a same-origin URL that serves a white PNG or SVG. The image loads asynchronously. During the load gap, feImage produces a transparent result, and the feBlend screen pass-through leaves the consent text visible. Once the image loads, the screen blend produces white-out. This timing creates a scan window: a scanner that evaluates the filter immediately after DOM construction may see the consent text visible (image not yet loaded) and report no finding. By the time the user interacts, the image is loaded and the attack is active.

Mitigation: after checking that a feImage href is present in a consent ancestor filter, add a load event listener and re-evaluate the filter on load.

SA-CSS-FIH-003: nested SVG with embedded filter chain

A data: URI SVG embedded via feImage href can itself contain a filter definition. The outer SVG's filter loads the inner SVG as an image; the inner SVG renders with its own filter applied. This creates a two-level filter chain. Each level may look innocuous in isolation: the outer filter might appear to be a simple feImage-feBlend composition; the inner SVG might appear to be a plain feColorMatrix. Evaluating the chain requires following the feImage href, parsing the embedded SVG, and evaluating its filter before evaluating the compositing step of the outer filter.

SA-CSS-FIH-004: JS mousedown href injection

The feImage element initially has no href attribute (or href=""). An empty href causes feImage to produce a transparent result. The downstream feBlend screen composites transparent against the consent source, leaving the consent text visible. On mousedown, the script sets href to the data: URI white SVG. The feBlend screen immediately produces a white-out. The user's pointer has already contacted the consent element at the moment the attack activates.

consentElement.addEventListener("mousedown", () => {
  const feImg = document.getElementById("consentFilter")
                         .querySelector("feImage");
  feImg.setAttribute("href",
    "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'>" +
    "<rect width='100%' height='100%' fill='white'/></svg>");
});

Filter tree inspection: The correct scanning approach for feImage attacks is to traverse the entire filter primitive tree, not just the top-level filter element. Any feImage in the filter subtree whose href resolves to a non-transparent image, combined with a compositing step that produces output overlying the consent source, is a potential white-out attack.

Attack summary

IDfeImage href typeCompositingEffectSeverity
SA-CSS-FIH-001data: URI white SVGfeBlend screenConsent text white-out; synchronousCritical
SA-CSS-FIH-002Same-origin URL (async)feBlend screenWhite-out after image load; scan-window evasionHigh
SA-CSS-FIH-003data: URI SVG with nested filterfeComposite overTwo-level chain; each level innocent in isolationHigh
SA-CSS-FIH-004Empty → data: URI (mousedown)feBlend screenWhite-out at click time; initial state normalCritical

Defences

SkillAudit findings for this attack surface

CRITICAL SA-CSS-FIH-001: feImage href = data: URI white SVG + feBlend mode="screen" — synchronous white-out; attack entirely within filter primitive chain; element src/href checks miss it; flag data: URI feImage in consent ancestor filters.
HIGH SA-CSS-FIH-002: feImage href = same-origin white image URL — white-out after async load; scan-window evasion during load gap; must evaluate filter after image load event, not only at parse time.
HIGH SA-CSS-FIH-003: feImage href = data: URI SVG with embedded filter — two-level filter chain; each level individually innocuous; evaluation requires following href, parsing embedded SVG, evaluating inner filter, then evaluating outer compositing step.
CRITICAL SA-CSS-FIH-004: JS mousedown sets feImage href from empty to data: URI white SVG — initial state normal (no-op feImage, consent text visible); white-out at click time; MutationObserver on href and xlink:href on all feImage in consent-ancestor filters required.

Related: feDisplacementMap xChannelSelector attacks  |  feBlend mode attacks  |  feComposite attacks  |  feFlood attacks

← Blog  |  Security Checklist