← Blog

MCP server SVG feComposite atop consent security

Three SA-CSS-ATOP consent attack patterns using feComposite operator="atop" — the Porter-Duff A-atop-B compositing formula out = in1 × αB + in2 × (1 − αA) exploited to erase consent elements, bleach anti-aliased glyph edges, and apply click-time erasure via flood-opacity JS override.

By SkillAudit  |  23 Oct 2026

The feComposite SVG filter primitive composites two images using a Porter-Duff formula selected by the operator attribute. The operator=atop formula is out = in1 × αB + in2 × (1 − αA), where αA is the alpha of in1 and αB is the alpha of in2 (SVG §15.13). This formula is geometrically the "A painted inside B" operation: in1 pixels survive only where in2 is opaque (αB), and in2 pixels fill the transparent regions of in1 (1 − αA).

The atop operator differs from the more commonly analyzed operator=in in a critical way: in uses out = in1 × αB (in1 survives only where in2 is opaque); atop additionally adds in2 × (1 − αA) (in2 fills where in1 is transparent). This additional term creates anti-aliased edge bleach attacks that have no equivalent in the in operator family.

Atop with transparent in2: When in2 has zero alpha everywhere (e.g., a feFlood with flood-opacity=0), both terms of the formula collapse: out = in1 × 0 + 0 × (1 − αA) = 0. Every output pixel is zero — both zero color and zero alpha. The element is fully transparent. This is the same erasure result as operator=in with a transparent mask, but achieved via the atop formula from a different code path.

SA-CSS-ATOP-001: feComposite operator="atop" with transparent in2 (full erasure)

A feFlood with flood-opacity="0" produces a fully transparent image. When used as in2 in an atop composite with in1=SourceGraphic, the formula gives: out = SourceGraphic × 0 + (flood-color) × (1 − αA). But the flood-color pixels have zero alpha (flood-opacity=0), so their RGB contribution is zero regardless of the (1 − αA) term. The output is fully transparent on all pixels.

<filter id="atop1">
  <feFlood flood-color="white" flood-opacity="0" result="transparentFlood"/>
  <feComposite in="SourceGraphic" in2="transparentFlood" operator="atop"/>
</filter>
<!-- αB=0: out = SourceGraphic×0 + transparent×(1-αA) = 0; full erasure -->

The distinction between this attack and the operator=in equivalent (SA-CSS-FCO-003: in with transparent mask) is in the code path, not the output. Detection must check operator=atop independently from operator=in; the erasure condition for atop is αB=0 on in2, identical to the in operator but with a different formula to evaluate.

SA-CSS-ATOP-002: operator="atop" with white opaque in2 (anti-aliased edge bleach)

When in2 is a white opaque feFlood (flood-color="white", flood-opacity=1, αB=1), the atop formula becomes: out = in1 × 1 + white × (1 − αA) = in1 + white × (1 − αA).

For fully opaque in1 pixels (αA=1): out = in1 + white × 0 = in1 — identity, the pixel is unchanged. These are the interior fully-opaque pixels of consent text.

For partially transparent in1 pixels (αA < 1): out = in1 + white × (1 − αA) — the in2 white bleaches toward white in proportion to (1 − αA). Anti-aliased glyph edges have αA values ranging continuously from 0 to 1. A glyph edge pixel with αA=0.3 receives out = 0.3 × in1 + 0.7 × white: the dark foreground color is mixed with 70% white.

<filter id="atop2">
  <feFlood flood-color="white" flood-opacity="1" result="whiteFlood"/>
  <feComposite in="SourceGraphic" in2="whiteFlood" operator="atop"/>
</filter>
<!-- αB=1: out = in1 + white×(1-αA); opaque pixels unchanged; anti-aliased edges bleached to white -->

WCAG at fully-opaque pixels is still passing: The interior opaque pixels of consent text are unchanged (αA=1 → identity). A WCAG contrast measurement that samples only fully-opaque foreground pixels will report the original contrast ratio. The attack destroys readability at the glyph edges — the anti-aliased transition zone — without affecting the interior. For typical sans-serif consent text at 14px, approximately 30–50% of visible pixels are anti-aliased edge pixels; bleaching these toward white causes the text to appear faded and low-contrast even when interior pixel contrast is nominally passing.

SA-CSS-ATOP-003: JS mousedown on feFlood flood-opacity 1 → 0 (click-time full erasure)

The initial state uses in2=feFlood with flood-opacity=1. With αB=1, the atop operator acts approximately as identity for opaque pixels (with edge bleach). The element renders with slightly degraded anti-aliasing. On mousedown, the script sets flood-opacity to 0: αB drops to 0, and SA-CSS-ATOP-001 takes effect — full erasure of all output pixels. The element disappears at click time.

consentElement.addEventListener("mousedown", () => {
  const flood = document.getElementById("consentFilter")
                         .querySelector("feFlood");
  flood.setAttribute("flood-opacity", "0"); // αB → 0; atop formula collapses to 0
  // full output erasure; element transparent; click proceeds on invisible element
});

The initial state (flood-opacity=1) is not a suspicious initial value — feFlood with flood-opacity=1 is a legitimate compositing input. Static analysis must check for the presence of a mousedown listener on the consent DOM subtree that targets feFlood flood-opacity within a consent ancestor filter. MutationObserver on the flood-opacity attribute of feFlood elements in such filters is the runtime defence.

Attack summary

IDin2 stateαBEffectSeverity
SA-CSS-ATOP-001feFlood flood-opacity=00Full output erasure → transparentCritical
SA-CSS-ATOP-002feFlood white flood-opacity=11Anti-aliased edge bleach → low contrastHigh
SA-CSS-ATOP-003flood-opacity 1→0 mousedown1→0Click-time full erasureCritical

Defences

SkillAudit findings for this attack surface

CRITICAL SA-CSS-ATOP-001: feComposite operator="atop" with transparent in2 (feFlood flood-opacity=0) — atop formula out=in1×αB+in2×(1-αA); αB=0 collapses first term to 0; αB=0 also makes flood pixels transparent; second term contributes 0; full output erasure; same effect as operator=in with transparent mask via different formula path.
HIGH SA-CSS-ATOP-002: feComposite operator="atop" with white opaque in2 (feFlood flood-opacity=1) — αB=1; out=in1+white×(1-αA); opaque pixels identity; anti-aliased glyph edges (partial αA) bleached toward white by (1-αA) factor; consent text readability destroyed at edges; WCAG interior-pixel check may still pass; edge-pixel sampling required.
CRITICAL SA-CSS-ATOP-003: JS mousedown sets feFlood flood-opacity from 1 to 0 within operator=atop composite — initial state: in2 opaque (αB=1); SA-CSS-ATOP-002 edge bleach effect; click-time: αB=0; SA-CSS-ATOP-001 full erasure; MutationObserver on feFlood flood-opacity in consent ancestor filters required.

Related: feComposite operator=arithmetic attacks  |  feComposite operator=over with white in2  |  feColorMatrix consent attacks

← Blog  |  Security Checklist