MCP server SVG filter region security
SVG filter effects operate under a two-tier region system. The <filter> element itself carries filterX, filterY, filterWidth, and filterHeight attributes that define the bounding box in which the entire filter computation occurs and beyond which filter output is clipped. Independently, each filter primitive — <feFlood>, <feComposite>, <feGaussianBlur>, and others — carries its own x, y, width, height subregion attributes that control which portion of the input image the primitive processes. Attackers exploit this two-tier system to achieve selective word erasure: the filter bounding box appears normal while primitive subregions are calibrated to the pixel positions of specific authorization terms. A check that flags filter presence without auditing primitive subregions misses the targeted erasure entirely.
Finding SA-FR-001: feFlood primitive subregion calibrated to cover only the authorization scope clause
<feFlood> primitive with explicitly set x, y, width, and height attributes in filterPrimitiveSubregion coordinates (same coordinate system as the filter region). The subregion is calibrated to cover the horizontal pixel band containing the authorization scope words ("all file system and network access") while leaving the header line and Agree button unaffected. The filter bounding box spans the full element area — a check that validates filterWidth and filterHeight as normal finds nothing suspicious. Only inspecting the primitive subregion and comparing it to the expected text layout geometry reveals the word-targeted erasure.<defs>
<filter id="word-erase" x="0%" y="0%" width="100%" height="100%"
filterUnits="objectBoundingBox">
<!-- Filter bounding box looks normal (full element coverage) -->
<!-- feFlood subregion targets y=55%..80% = the authorization scope line -->
<feFlood flood-color="white" flood-opacity="1"
x="0%" y="55%" width="100%" height="25%"
result="erasure"/>
<feComposite in="SourceGraphic" in2="erasure"
operator="over" result="composited"/>
<feComposite in="erasure" in2="SourceGraphic" operator="in"
x="0%" y="0%" width="100%" height="55%"
result="topHalf"/>
<feMerge>
<feMergeNode in="composited"/>
<feMergeNode in="topHalf"/>
</feMerge>
</filter>
</defs>
<g filter="url(#word-erase)">
<text x="10" y="30" font-size="14" fill="#111">Permission request:</text>
<text x="10" y="55" font-size="14" fill="#111">This MCP server requires</text>
<!-- The next line (y≈75) falls in the erased subregion (y=55%..80%) -->
<text x="10" y="75" font-size="14" fill="#111">all file system and network access</text>
</g>
The filter bounding box (x="0%" y="0%" width="100%" height="100%") spans the full element — this is the expected default and raises no flag. The attack lives inside the <feFlood> primitive's own y="55%" height="25%" subregion, which targets the second half of the consent element. Users read the top portion ("Permission request: This MCP server requires") and see the Agree button; the authorization scope clause is erased by the feFlood white flood in the lower subregion. Detection requires mapping primitive subregion coordinates to the consent text layout and checking whether any line of authorization scope falls in a flood-covered band.
Finding SA-FR-002: near-zero filterWidth creating a 1px-wide erasure strip that passes coverage checks
filterWidth="0.002" in filterUnits="objectBoundingBox" coordinates, equivalent to 0.2% of the element width. For a 400px wide consent element this is a 0.8px wide filter region. The contained feFlood erasure primitive applies to less than 1px of the element's area — imperceptible to users and producing no visible effect. An automated check that validates the presence of a filter without measuring the filter region's effective pixel coverage flags this as an erasure filter when the actual erasure area is sub-pixel. Conversely, a sophisticated attacker can use this to accumulate false-positive fatigue: a large number of near-zero-coverage filters on legitimate elements desensitizes the auditor to filter findings while a full-coverage erasure filter elsewhere goes unnoticed.<defs>
<!-- filterWidth=0.002 → 0.8px wide at 400px element width -->
<!-- Filter technically "present and active" but covers sub-pixel area -->
<filter id="micropatch" x="0" y="0"
filterUnits="objectBoundingBox"
filterWidth="0.002" filterHeight="1">
<feFlood flood-color="white" flood-opacity="1"/>
<feComposite in2="SourceGraphic" operator="in"/>
</filter>
<!-- Real attack filter with full coverage -->
<filter id="real-erase" x="0" y="0" width="1" height="1"
filterUnits="objectBoundingBox">
<feFlood flood-color="white" flood-opacity="1"/>
<feComposite in2="SourceGraphic" operator="in"/>
</filter>
</defs>
<text id="safeElement" filter="url(#micropatch)" ...>Irrelevant UI label</text>
<text id="consentText" filter="url(#real-erase)" ...>I grant all requested permissions</text>
Detection based on "does this element have a filter?" produces the same finding for id="safeElement" (0.8px coverage, no user impact) and id="consentText" (full coverage, consent erasure). Effective detection requires computing the effective pixel area of each filter (filterWidth × filterHeight in user-space pixels) relative to the element's bounding box area, and only flagging filters whose effective coverage area overlaps with consent text rendering bounds. Coverage ratio < 1% should be flagged as anomalous noise rather than erasure.
Finding SA-FR-003: filterUnits=userSpaceOnUse with coordinates placing the region outside the element bounding box
filterUnits="userSpaceOnUse", the filterX, filterY, filterWidth, filterHeight values are in the SVG user coordinate system rather than as fractions of the element bounding box. An attacker declares filterX="-5000" filterY="-5000" filterWidth="1" filterHeight="1" — placing the 1×1px filter region at SVG coordinates (−5000, −5000), far outside the viewport and the element. The filter is attached to the consent text element via filter="url(#offscreen-filter)". Because the filter region does not intersect the element's bounding box, the filter produces no output for that element — the element renders without any filter effect applied. Auditors see the filter attribute present and may assume a filter effect is active; in reality the filter region mismatch makes the filter a no-op on the consent text.<defs>
<!-- filterUnits=userSpaceOnUse: coordinates are SVG user space, not fractions -->
<!-- filterX=-5000, filterY=-5000 → filter region is 10000px off-screen -->
<filter id="offscreen-filter"
filterUnits="userSpaceOnUse"
x="-5000" y="-5000" width="1" height="1">
<feFlood flood-color="white" flood-opacity="1"/>
<feComposite in2="SourceGraphic" operator="in"/>
</filter>
</defs>
<!-- Consent element at SVG coordinates x=10..410, y=20..80 -->
<!-- Filter region at (-5000,-5000) does not intersect — no filter effect applied -->
<g filter="url(#offscreen-filter)">
<text x="10" y="45" font-size="14" fill="#111">
I authorize all file system and network access including credentials
</text>
</g>
This is a "phantom filter" — the element has a filter attribute pointing to a declared filter, but the filter's filterUnits="userSpaceOnUse" region does not intersect the element's bounding box. The element renders as if no filter were applied. This technique is used in two scenarios: (1) to make a consent text element appear to have a legitimate-seeming filter applied (for audit obfuscation of a different attack), or (2) as a reference for how not to detect filter attacks — checkers that verify "filter attribute present → filter active" will mis-classify this no-op. Detection must resolve the effective filter region in user-space coordinates and compute intersection with the element bounding box.
Finding SA-FR-004: SMIL animate switching filter region from safe to attack dimensions at interaction event
<animate> children targeting the filterWidth and filterHeight attributes. At page load the filter region is filterWidth="0.001" filterHeight="0.001" — near-zero coverage, no visible effect. At begin="consentForm.focus" the animate element transitions to filterWidth="1.4" filterHeight="1.4" with fill="freeze" and dur="0.001s". When the user focuses the consent form (e.g., by clicking anywhere within it), the filter region instantly expands to cover the full element plus margins, activating the contained feFlood erasure. The initial near-zero state is what a static audit snapshot captures; the post-focus state is what the user sees when interacting with the consent form.<defs>
<filter id="focus-activated"
filterUnits="objectBoundingBox"
x="-20%" y="-20%">
<!-- filterWidth/Height start near-zero → no erasure on page load -->
<!-- At consentForm.focus they expand to 1.4 → full-coverage erasure -->
<animate attributeName="filterWidth"
from="0.001" to="1.4"
begin="consentForm.focus" dur="0.001s" fill="freeze"/>
<animate attributeName="filterHeight"
from="0.001" to="1.4"
begin="consentForm.focus" dur="0.001s" fill="freeze"/>
<feFlood flood-color="white" flood-opacity="1" result="white"/>
<feComposite in="white" in2="SourceGraphic" operator="in"/>
</filter>
</defs>
<g id="consentForm" filter="url(#focus-activated)">
<text x="10" y="40" font-size="14" fill="#111">
Granting access allows this MCP server to read all files in your home directory
</text>
<rect id="agreeBtn" x="260" y="55" width="120" height="32" fill="#2563eb" rx="6"/>
<text x="320" y="77" font-size="13" fill="white" text-anchor="middle"
pointer-events="none">Agree</text>
</g>
The SMIL animate on the filter element's own filterWidth/filterHeight attributes is distinct from animating a filter primitive's output. A static snapshot sees filterWidth=0.001 (near-zero, no effect). The animate element's from value and the initial filter state match — there is no discrepancy between attribute and animate initial state. Detection requires: enumerating animate elements that are children of filter elements (not inside a primitive), identifying those that modify region attributes, evaluating both the initial value (near-zero → no effect) and the post-trigger value (1.4 → full erasure), and flagging the combination as an interaction-activated filter expansion.
Filter region vs filter primitive subregion: audit scope comparison
| Region type | Element / attributes | Coordinate system | Default value | Audit requirement |
|---|---|---|---|---|
| Filter bounding box | <filter> element: filterX, filterY, filterWidth, filterHeight |
filterUnits: objectBoundingBox (fraction of element bbox) or userSpaceOnUse (SVG user coordinates) |
x="-10%" y="-10%" width="120%" height="120%" |
Resolve to user-space pixels; intersect with element bounding box; check SMIL animate children |
| Primitive subregion | Each primitive (<feFlood>, <feComposite>, etc.): x, y, width, height |
primitiveUnits: userSpaceOnUse (default) or objectBoundingBox |
Inherits from filter bounding box (full coverage within filter) | Map to user-space pixels; compare to consent text line y-ranges; flag any flood covering consent lines |
| primitiveUnits coordinate mode | primitiveUnits attribute on <filter> |
Controls interpretation of all primitive subregion values | userSpaceOnUse |
Read primitiveUnits before interpreting primitive x/y/width/height values |
| SMIL animate on region | <animate> inside <filter>, not inside a primitive |
Same as filter bounding box coordinate system | No animation (static) | Enumerate animate elements at direct filter child level; evaluate to/from values against coverage threshold |
Detection algorithm: SVG filter region audit for consent elements
| Step | Action | What it catches |
|---|---|---|
| 1 | For each filter referenced by a consent-subtree element, resolve filterUnits. If userSpaceOnUse, read x/y/width/height as SVG user coordinates; if objectBoundingBox, multiply fractions by element bbox dimensions |
Establishes correct coordinate frame for all subsequent region comparisons |
| 2 | Intersect the resolved filter bounding box (user-space) with the element bounding box. If intersection area < 5% of element area → flag High (phantom filter or near-zero coverage) | SA-FR-002 (near-zero filterWidth), SA-FR-003 (offscreen filterUnits=userSpaceOnUse region) |
| 3 | For each <feFlood> primitive in consent-subtree filters, resolve primitive subregion using primitiveUnits. Map the resolved subregion to consent element y-ranges (e.g., which text lines fall in y=55%..80%). Flag Critical if any text line containing authorization scope falls within a white-flood subregion |
SA-FR-001 (primitive subregion word targeting) |
| 4 | Enumerate <animate> elements that are direct children of <filter> elements (not of primitives). For those targeting filterWidth, filterHeight, x, or y, evaluate both the initial/from value and the to/by value. Flag High if to value represents full coverage while from is near-zero (interaction-activated expansion) |
SA-FR-004 (SMIL animate expanding filter region at interaction) |
| 5 | Check for primitive subregion SMIL animate children (animate targeting primitive x/y/width/height). Apply same from/to coverage evaluation as step 4 | SMIL animation on primitive subregion (complementary to filter-level animation) |
SkillAudit resolves both filter-level and primitive-level region coordinates to user-space pixels, computes coverage overlap with consent text layout geometry, and evaluates SMIL animate children on region attributes at both levels. Partial-erasure filters targeting specific consent words are detected by mapping primitive subregions to text line y-ranges. Run a free audit on your MCP server's GitHub URL.