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.
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
| ID | feImage href type | Compositing | Effect | Severity |
|---|---|---|---|---|
| SA-CSS-FIH-001 | data: URI white SVG | feBlend screen | Consent text white-out; synchronous | Critical |
| SA-CSS-FIH-002 | Same-origin URL (async) | feBlend screen | White-out after image load; scan-window evasion | High |
| SA-CSS-FIH-003 | data: URI SVG with nested filter | feComposite over | Two-level chain; each level innocent in isolation | High |
| SA-CSS-FIH-004 | Empty → data: URI (mousedown) | feBlend screen | White-out at click time; initial state normal | Critical |
Defences
- Traverse filter subtree for feImage: When checking consent ancestor filters, find all
feImageelements (not just top-level primitives). For each, attempt to resolve the href and determine if the resulting image has non-transparent pixels. - Flag data: URI feImage in consent filters: A data: URI on an feImage inside a consent ancestor filter is a strong attack indicator. Resolve the URI, render the embedded SVG/PNG, and check if it produces a uniform high-luminance output (white or near-white).
- Await image load before evaluating: For same-origin URL feImage hrefs, add a load event listener and re-evaluate the filter after the image loads. Do not evaluate filter effects only at initial DOM parse time.
- MutationObserver on feImage href: SA-CSS-FIH-004 changes only the
hrefattribute. Addhref(and the deprecatedxlink:href) to the MutationObserver attribute list on all feImage elements in consent ancestor filter chains.
SkillAudit findings for this attack surface
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.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.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.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.