← Blog

MCP server SVG filter region x/y/width/height consent security

Four SA-CSS-FRG consent attack patterns exploiting the filter element's x, y, width, and height region attributes — zero-area filter produces transparent output per the SVG spec, partial coverage creates deceptive sub-region processing, expanded region enables off-element displacement capture — with JS region shrink for click-time attacks.

By SkillAudit  |  23 Oct 2026

The SVG <filter> element's x, y, width, and height attributes define the filter region — the bounding box within which the filter is applied. These attributes are distinct from the filter primitive chain: they determine which pixels enter and exit the filter pipeline. The SVG specification defines specific behavior at boundary conditions: a zero-area region produces transparent output. This boundary condition, combined with the ability to set precise sub-region coverage and extreme expanded regions, creates four distinct consent attack surfaces that are independent of the filter primitive chain contents.

SVG spec zero-area rule: W3C SVG specification section 13.7 states: "If width=0 or height=0, then the primitive has no effect (i.e., the result is a transparent black image)." This applies to the filter element itself, not to individual filter primitives. A filter with width="0" produces a transparent output for the entire filtered element, regardless of the filter chain contents.

SA-CSS-FRG-001: Zero-area filter region (width=0 or height=0)

Setting width="0" or height="0" on the <filter> element invokes the SVG spec zero-area rule: the filter output is transparent black. The entire consent element becomes transparent. This is the most minimal filter-region attack — it requires no filter primitive chain at all. An empty filter body (<filter id="f"></filter>) with width="0" or height="0" achieves complete transparency. The element retains its layout, DOM presence, textContent, event handlers, and BCR. Because the filter element itself appears to have no primitives, analysis focused on primitive-chain attacks misses this entirely.

<!-- CRITICAL: empty filter chain + zero width = transparent consent element -->
<filter id="frg1" x="-10%" y="-10%" width="0%" height="120%">
  <!-- no primitives needed: width=0 makes output transparent per SVG spec -->
</filter>
<div style="filter: url(#frg1)">
  I consent to the above terms of service.
  <!-- Fully transparent. Element present. textContent intact. BCR unchanged. -->
</div>

SA-CSS-FRG-002: Partial coverage — filter only applied to sub-region

With x="50%" y="50%" width="50%" height="50%", the filter region is the bottom-right quadrant of the element bounding box. SVG spec behavior for pixels outside the filter region: they are rendered using the SourceGraphic (unfiltered passthrough). If the consent text is laid out with the main content in the top portion (typical for a single-line or two-line button), the top half renders normally while the bottom-right quadrant is processed by the filter chain. A white-out filter chain applied only to the bottom-right quadrant produces a split rendering: the top-left half visible (shows "I agree" or similar), the bottom-right half white-out. On a white-background MCP client, the bottom-right quadrant is invisible regardless — so the partial filter appears to produce a "normal" rendering while potentially hiding legally significant content that appears in the filtered sub-region.

<filter id="frg2" x="50%" y="50%" width="50%" height="50%"
        filterUnits="objectBoundingBox">
  <feFlood flood-color="white" flood-opacity="1"/>
</filter>
<!-- Pixels in top-left 75% of element: unfiltered (SourceGraphic passthrough) -->
<!-- Pixels in bottom-right 25%: processed by feFlood → white → invisible on white bg -->

SA-CSS-FRG-003: Massively expanded region for displacement capture

The default filter region is x="-10%" y="-10%" width="120%" height="120%" — a 10% margin around the element bounding box. For feDisplacementMap attacks with large scale values (e.g., scale=500), displaced pixels can move up to ±250px from their original positions. If the displacement moves pixels outside the default filter region, those pixels are clipped and lost (they do not appear at the displaced location). An attacker who uses an expanded filter region (x="-5000%" y="-5000%" width="10100%" height="10100%") ensures that all displaced pixels, regardless of displacement magnitude, remain within the filter region and can be captured or composited by later primitives. This is a setup primitive for large-scale displacement attacks, not an attack on its own, but its presence in a consent element filter should trigger investigation of the full displacement chain.

<!-- Expanded region: enables off-element displacement capture -->
<filter id="frg3" x="-5000%" y="-5000%"
        width="10100%" height="10100%"
        filterUnits="objectBoundingBox">
  <feTurbulence baseFrequency="0.01" numOctaves="1" seed="7" result="noise"/>
  <feDisplacementMap in="SourceGraphic" in2="noise" scale="500"
    xChannelSelector="R" yChannelSelector="G"/>
  <!-- Without expanded region: displaced pixels outside -10%/+120% are clipped -->
  <!-- With expanded region: all displaced pixels retained → text moved far off position -->
</filter>

Extreme filter region dimensions: Flag filter elements with width or height values greater than 500% or less than 0.1% on consent ancestor elements. Both extremes signal attack patterns: near-zero dimensions invoke the transparent output rule, extreme large dimensions are setup for large-scale displacement capture.

SA-CSS-FRG-004: JS mousedown filter region shrink (width 120% → 0%)

Initial state: the filter has width="120%" — normal filter region, any filter primitives applied normally (or an empty chain that passes through the source). Consent text is visible. On mousedown, the script sets width to "0%" on the filter element. The SVG zero-area rule immediately makes the entire consent element transparent. This attack requires no changes to the filter primitive chain — the attacker modifies only the filter's region dimensions. Because the initial state has a normal-looking filter region and a benign (or empty) primitive chain, static analysis of the initial DOM produces no findings.

consentEl.addEventListener("mousedown", () => {
  const filterEl = document.getElementById("consentFilter");
  filterEl.setAttribute("width", "0%"); // zero-area rule → transparent output
  // No primitive changes needed — region shrink alone triggers spec behavior
});

Attack summary

IDRegion attributeValueMechanismEffectSeverity
SA-CSS-FRG-001width or height0 or 0%SVG spec zero-area transparent ruleEntire element transparentCritical
SA-CSS-FRG-002x, y, width, height50%, 50%, 50%, 50%Partial coverage — sub-region filterConsent content in filtered sub-region white-outedHigh
SA-CSS-FRG-003x, y, width, height-5000%, -5000%, 10100%, 10100%Expanded region enables displacement captureOff-element displacement not clippedHigh
SA-CSS-FRG-004width120%→0% (mousedown)Zero-area rule at click timeElement transparent at click timeCritical

Defences

SkillAudit findings for this attack surface

CRITICAL SA-CSS-FRG-001: SVG filter width="0" or height="0" on consent element filter — W3C SVG spec §13.7 zero-area transparent output rule; entire element transparent; no filter primitive chain required; empty filter body with zero-dimension region achieves complete erasure; evades all primitive-chain analysis; element present in DOM; textContent and BCR unchanged.
HIGH SA-CSS-FRG-002: SVG filter x="50%" y="50%" width="50%" height="50%" on consent element — filter applied only to bottom-right quadrant; top-left 75% of element rendered as unfiltered SourceGraphic passthrough; white-out filter chain applied to bottom-right quadrant hides legally significant content that may appear there; visually ambiguous — consent text in unfiltered region appears but filtered content invisible.
HIGH SA-CSS-FRG-003: SVG filter region x="-5000%" y="-5000%" width="10100%" height="10100%" — expanded region setup for large-scale feDisplacementMap attacks; displaced pixels outside default ±10% margin are retained rather than clipped; enables consent text displacement far outside element without pixel loss; flag and investigate full displacement chain.
CRITICAL SA-CSS-FRG-004: JS mousedown sets filter width from "120%" to "0%" — zero-area rule activates at mousedown; element transparent at click time; primitive chain unchanged; initial state DOM analysis produces no findings; MutationObserver on width/height attributes of <filter> elements in consent ancestor filter references required.

Related: feDisplacementMap scale attacks  |  feTurbulence anisotropic displacement  |  feFlood consent attacks  |  filterUnits coordinate system attacks

← Blog  |  Security Checklist