MCP server CSS flood-color security: SVG feFlood filter background overlay, transparent flood masking, CSS vs attribute override, and feComposite erasure on consent text

Published 2026-09-26 — SkillAudit Research

The SVG feFlood filter primitive fills the entire filter region with a solid color, defined by the flood-color and flood-opacity CSS properties (also available as SVG presentation attributes). feFlood is a source primitive — it generates a filled region that can then be composited with the element's rendered output using feComposite or feBlend.

When an SVG filter applied to a consent text element includes a feFlood primitive composited over the source graphic, the flood region paints on top of the text. If flood-color matches the page background and flood-opacity is 1, the text is completely obscured — replaced by a solid background-colored rectangle. CSS flood-color can override the SVG attribute via the cascade, making this attack invisible to attribute-reading auditors.

Filter graph audit requirement: Standard visibility checks (display, visibility, opacity, dimensions) do not detect feFlood overlay attacks. The consent text element has positive dimensions, is not hidden, and has non-zero opacity. The attack operates via the CSS filter rendering pipeline — the element's pixel output is replaced by the feFlood result before compositing into the page. Auditing requires traversing the filter reference graph and checking feFlood primitives within any filter applied to consent text elements.

Attack findings

CRITICAL
feFlood with background-matching flood-color composited over consent text via feComposite operator="over" — text replaced by solid background rectangle
An SVG filter applied to consent text uses feFlood to fill the filter region with the page background color, then feComposite with operator="over" to place the flood region on top of the source graphic. The flood paints the entire filter bounding box in the background color — the text is fully obscured. The element has positive getBoundingClientRect dimensions (the flood fills the same bounding box as the text). The filter attribute is set on the text element. The text exists in the DOM with non-null content. All standard checks pass; only examining the filter graph reveals the overlay.
<defs>
  <filter id="consent-erase" x="0" y="0" width="100%" height="100%">
    <!-- Generate the source text rendering -->
    <feFlood flood-color="#ffffff" flood-opacity="1" result="bg"/>
    <!-- Composite flood (bg) OVER the SourceGraphic (text) -->
    <feComposite in="bg" in2="SourceGraphic" operator="over"/>
  </filter>
</defs>
<text filter="url(#consent-erase)">
  By clicking Accept, you agree to binding arbitration.
</text>

/* Result:
   feFlood generates white rectangle (flood-color: #ffffff)
   feComposite places white rectangle OVER text → text invisible
   getBoundingClientRect: non-zero (flood fills same region) → passes size check
   getComputedStyle(textEl).filter: "url(#consent-erase)" → reveals filter reference
   feFlood.flood-color computed: "rgb(255,255,255)" → background match → CRITICAL */
HIGH
CSS flood-color overrides SVG attribute — attribute shows innocuous color; computed value reveals background-matching override
An MCP server sets flood-color="rgba(0,0,0,0)" as an SVG attribute on a feFlood primitive (a transparent flood — innocuous). A CSS rule overrides this: #consent-erase feFlood { flood-color: #ffffff; }. The rendered flood is an opaque white rectangle composited over the text. An auditor reading feFlood.getAttribute('flood-color') finds "rgba(0,0,0,0)" — transparent, no concern. Only getComputedStyle(feFlood).floodColor returns "rgb(255,255,255)", revealing the CSS override. This follows the same cascade override pattern as stop-color and fill-opacity attribute attacks.
<!-- SVG attribute: transparent (innocuous) -->
<feFlood id="consent-flood" flood-color="rgba(0,0,0,0)" flood-opacity="1"/>

/* CSS cascade override: background-matching */
#consent-erase feFlood {
  flood-color: #ffffff;    /* overrides transparent attribute */
}

/* getAttribute('flood-color'):         "rgba(0,0,0,0)" → transparent → PASS (incorrect)
   getComputedStyle(feFlood).floodColor: "rgb(255,255,255)" → white → ATTACK DETECTED
   CSS author stylesheet wins over SVG presentation attribute in cascade */
MEDIUM
feFlood used with feBlend mode="normal" to composite opaque rectangle over consent text — blend replaces text pixels
feBlend with mode="normal" composites the foreground layer over the background layer, replacing background pixels at positions where the foreground has non-zero alpha. An MCP server uses feFlood to generate a solid background-colored rectangle, then feBlend with mode="normal" and the flood as the foreground. The flood's opaque pixels replace all text pixels. A superficial filter inspection that checks for feBlend but expects image-effect modes (multiply, screen, overlay) may not flag a mode="normal" feBlend as an attack vector — it appears to be a standard compositing operation.
<filter id="consent-blend-erase">
  <!-- Flood produces background-colored rectangle -->
  <feFlood flood-color="#f8f8f8" flood-opacity="1" result="overlay"/>
  <!-- feBlend mode=normal: foreground (overlay) replaces SourceGraphic (text) pixels -->
  <feBlend in="overlay" in2="SourceGraphic" mode="normal"/>
</filter>

/* feBlend mode=normal compositing: Dst = Src · αSrc + Dst · αDst · (1 - αSrc)
   αSrc = 1 (flood is fully opaque) → Dst = Src (flood color)
   All text pixels replaced by flood color → text invisible
   Auditor checking feBlend.mode: "normal" may not flag as attack (legitimate use: sprite compositing)
   Attack revealed by: flood-color == page background color */
MEDIUM
feFlood with animated flood-color — innocuous at scan time; background-matching at button activation delay
A CSS animation changes flood-color from a transparent or neutral value (visible at page load) to the background color (invisible at button activation time). At the moment the user clicks Accept, the filter flood covers the text. This is the filter-layer variant of the timing attack pattern. A static scan at DOMContentLoaded sees a transparent flood and no concern. Detection requires simulating the CSS animation timeline to the button activation delay time and re-reading computed flood-color.
/* Innocent at page load */
@keyframes flood-reveal {
  0%   { flood-color: transparent; }    /* load: innocuous, text visible */
  80%  { flood-color: transparent; }    /* most of delay: text visible */
  100% { flood-color: #ffffff; }        /* at button activation: text invisible */
}

#consent-erase feFlood {
  animation: flood-reveal 3s forwards;    /* matches button activation delay */
  flood-opacity: 1;
}

/* Static audit at t=0: flood-color=transparent → no concern
   At t=3s (button activates): flood-color=#ffffff → text obscured
   Detection: simulate animation timeline; check flood-color at animation-end-time */

Detection

function checkFeFloodFilters(svgRoot, pageBackground = '#ffffff') {
  const findings = [];

  /* Find all consent text elements with filters applied */
  const textEls = svgRoot.querySelectorAll('text, tspan, [data-consent]');
  for (const el of textEls) {
    const filterRef = el.getAttribute('filter') || getComputedStyle(el).filter;
    if (!filterRef || filterRef === 'none') continue;

    const filterId = (filterRef.match(/url\(#([^)]+)\)/) || [])[1];
    if (!filterId) continue;

    const filter = svgRoot.getElementById(filterId);
    if (!filter) continue;

    /* Traverse filter graph for feFlood primitives */
    const floods = filter.querySelectorAll('feFlood');
    for (const flood of floods) {
      /* Must use getComputedStyle — CSS may override SVG attribute */
      const cs = getComputedStyle(flood);
      const floodColor   = cs.floodColor   || flood.getAttribute('flood-color')   || 'black';
      const floodOpacity = parseFloat(cs.floodOpacity || flood.getAttribute('flood-opacity') || '1');

      /* Check for CSS vs attribute mismatch */
      const attrColor = flood.getAttribute('flood-color') || '';
      if (attrColor && cs.floodColor && attrColor !== cs.floodColor) {
        findings.push({
          severity: 'high', flood,
          issue: `flood-color attribute "${attrColor}" overridden by CSS "${cs.floodColor}" — attribute audit sees wrong color`
        });
      }

      /* Check if flood is opaque and background-matching */
      const bgRGB = parseColorToRGB(pageBackground);
      const floodRGB = parseColorToRGB(floodColor);
      if (floodOpacity > 0.8 && floodRGB && bgRGB && colorsMatch(floodRGB, bgRGB, 20)) {
        /* Check compositing operator: is flood placed over SourceGraphic? */
        const composite = flood.parentElement?.querySelector('feComposite, feBlend');
        findings.push({
          severity: 'critical', flood,
          issue: `feFlood with background-matching flood-color (${floodColor}) at opacity=${floodOpacity} — likely composited over consent text to erase it`
        });
      }
    }
  }

  return findings.length ? findings : null;
}

Remediation

ControlHow it helps
Traverse the filter reference graph for all SVG filters applied to consent text elements and check feFlood primitives within themStandard visibility checks do not detect filter-layer attacks; only examining the filter graph reveals feFlood overlay patterns
Read flood-color via getComputedStyle(feFlood).floodColor, not via getAttribute('flood-color')CSS cascade overrides SVG presentation attributes; the CSS-computed flood color is the value used in rendering
Compare computed flood-color against the page background color and flag near-matches with flood-opacity > 0.5Background-matching floods composited over text are the primary erase attack vector; contrast comparison between flood color and background reveals camouflage
Check feComposite and feBlend operators in the filter graph to determine whether a flood region is placed over or under the source graphicfeFlood under SourceGraphic (in2="SourceGraphic", operator="in") is a masking pattern; feFlood over SourceGraphic is an overlay erase — the compositing operator determines attack direction

SkillAudit traverses SVG filter reference graphs on consent text elements and checks feFlood primitives for background-matching flood colors, CSS cascade overrides, and compositing operators that place flood regions over source text. Run a free audit on any MCP server GitHub URL to detect feFlood overlay attacks and the full SVG filter consent manipulation surface.