← Blog

MCP server SVG feOffset displacement consent security

Four SA-SVG-FOF consent attack patterns using the SVG feOffset primitive to displace consent text within filter pipelines. feOffset translates its input image by a specified dx/dy pixel offset within the filter region. When the offset exceeds the filter primitive subregion dimensions or the SVG viewport, displaced content is clipped: the consent text is moved out of the visible filter output but remains in the DOM, leaving the element present with non-zero layout dimensions and passing all CSS property checks.

By SkillAudit  |  30 Oct 2026

The SVG feOffset primitive translates its input image by dx pixels horizontally and dy pixels vertically within the filter coordinate system. The filter region (defined by x, y, width, height on the <filter> element) determines the area within which primitives operate. By default the filter region extends slightly beyond the element bounding box to accommodate effects like blur. The critical behavior: content translated outside the filter region's bounds is clipped.

SA-SVG-FOF-001: feOffset large dx — filter boundary displacement

A feOffset dx="1920" dy="0" primitive displaces the SourceGraphic 1920 pixels to the right within the filter coordinate system. The default SVG filter region extends the element bounding box by 10% on each side (per SVG spec default x="-10%" y="-10%" width="120%" height="120%"). If the consent text element is 400px wide, the filter region extends to approximately 440px wide. Displacing by 1920px moves the entire SourceGraphic far outside the filter region's right boundary. Content outside the filter region is clipped: the filter output is empty.

<filter id="fof-001" x="-10%" y="-10%" width="120%" height="120%">
  <feOffset in="SourceGraphic" dx="1920" dy="0"/>
</filter>

<p class="consent-text" style="filter:url(#fof-001)">
  By clicking Allow you grant file system access.
</p>

<!-- Filter region analysis:
     Element bounding box: x=100, y=200, width=400, height=30
     Default filter region (objectBoundingBox units):
       x = 100 - 10% of 400 = 100 - 40 = 60
       y = 200 - 10% of 30 = 200 - 3 = 197
       width = 400 * 120% = 480
       height = 30 * 120% = 36

     feOffset dx=1920: SourceGraphic moved 1920px right in filter coords
     Displaced position: x = 60 + 1920 = 1980, well outside filter right edge (60+480=540)
     → Entire displaced image is outside filter region bounds
     → Filter output: empty (transparent rectangle)

     DOM / layout checks:
     - element.getBoundingClientRect(): returns original bounding box (layout unchanged)
     - element.offsetWidth / offsetHeight: original values (layout unchanged)
     - getComputedStyle(el).filter: returns "url(#fof-001)" (detectable but requires parsing)
     - Rendered pixels: fully transparent / page background color

     Detection: check for feOffset with |dx| > element.offsetWidth OR |dy| > element.offsetHeight
     → SA-SVG-FOF-001 CRITICAL -->

Layout box present, visual rendering absent: feOffset displacement operates inside the filter pipeline before the element's painted output is composited onto the page. The element's layout box (position, size) is determined before filtering. An element with a filter that displaces content to outside the filter region has a normal layout box and passes getBoundingClientRect, offsetWidth, and offsetHeight checks, but renders as a transparent rectangle in its original position.

CRITICAL
SA-SVG-FOF-001 — feOffset dx="1920" on consent text: displaces SourceGraphic 1920px outside filter region; filter output empty; element layout box intact; getBoundingClientRect passes; rendered pixels transparent. Detection: feOffset |dx| or |dy| exceeding element layout dimension → CRITICAL.

SA-SVG-FOF-002: feOffset with SVG overflow=hidden viewport clipping

When the consent text is rendered within an SVG element (e.g., an <svg> element containing a <text> node), the SVG element's own overflow attribute determines whether displaced filter output is clipped at the SVG viewport. The default SVG overflow for inline SVG is hidden. A feOffset within a filter applied to an SVG <text> element can displace the text outside the SVG viewport, which then clips the output.

<svg width="400" height="30" overflow="hidden">
  <defs>
    <filter id="fof-002">
      <feOffset in="SourceGraphic" dx="500" dy="0"/>
    </filter>
  </defs>
  <text x="10" y="20" filter="url(#fof-002)"
        fill="#111111" font-size="14">
    By clicking Allow you grant file system access.
  </text>
</svg>

<!-- SVG overflow clipping analysis:
     SVG viewport: width=400, height=30
     Text x=10, text width ≈ 380px
     feOffset dx=500: text displaced to x=510, right edge at x=890
     SVG overflow=hidden: content outside [0,0,400,30] is clipped

     Result: entire text block is at x=510..890, fully outside x=[0..400]
     → All text pixels clipped by SVG overflow=hidden
     → SVG element renders as empty 400×30 area

     Note: SVG overflow=visible would allow the displaced text to paint outside the SVG
     box and be visible on the page. Attacker must use overflow=hidden (or default) to clip.

     Detection:
     1. Find SVG text/tspan elements with filter attribute
     2. Check if applied filter contains feOffset primitive
     3. Compute displaced position relative to SVG viewport
     4. If displaced region has zero intersection with SVG viewport → CRITICAL -->
CRITICAL
SA-SVG-FOF-002 — feOffset dx="500" on SVG <text> inside overflow="hidden" SVG viewport: displaced text falls outside [0,SVG.width]; SVG clips at viewport; zero visible text. Detection: compute offset text position vs SVG viewport bounds → CRITICAL if zero intersection.

SA-SVG-FOF-003: feOffset + feComposite operator=in re-clipping pipeline

A sophisticated pipeline chains feOffset with feComposite operator="in" to create an attack that requires understanding the full filter graph. First, feOffset displaces SourceGraphic by a large amount. Then, feComposite in="offset-result" in2="SourceGraphic" operator="in" re-clips the displaced content back to the original SourceGraphic alpha region. The mathematical result of compositing an offset image with the original alpha using operator=in depends on whether the original and offset glyph alpha regions overlap: with a displacement larger than the glyph width, they do not, and the intersection is empty.

<filter id="fof-003">
  <feOffset in="SourceGraphic" dx="200" dy="0" result="shifted"/>
  <feComposite in="shifted" in2="SourceGraphic" operator="in"/>
</filter>

<!-- Complex pipeline analysis:
     Step 1: feOffset dx=200 → SourceGraphic shifted 200px right → result "shifted"
     Step 2: feComposite in="shifted" in2="SourceGraphic" operator="in"
       → Output alpha = min(shifted.alpha_at(x,y), SourceGraphic.alpha_at(x,y))
       → At original glyph position (x,y): shifted.alpha = 0 (text moved away)
         → min(0, text_alpha) = 0 → no output at original position
       → At shifted glyph position (x+200, y): SourceGraphic.alpha = 0 (empty space)
         → min(text_alpha, 0) = 0 → no output at shifted position either
       → Result: empty output — no visible pixels anywhere

     This produces consent text erasure without any flood colors or blend modes.
     The attack is structurally different: it uses geometric non-overlap rather than
     color mathematics.

     Detection challenge:
     Static analysis must trace the data flow: which primitive's output feeds in2?
     What is the original bounding box of the source image?
     Does the offset guarantee non-overlap with the in2 alpha?
     → Requires simulating the filter geometry, not just checking property values. -->

Geometric intersection attack: SA-SVG-FOF-003 exploits the geometric non-overlap between the offset image and the original alpha mask. With a displacement larger than the element width, the intersection is always empty, producing no visible output. This attack requires no flood colors, no blend mode mathematics — only displacement exceeding the element's text width. Detection requires simulating the filter geometry or using Canvas pixel sampling.

CRITICAL
SA-SVG-FOF-003 — feOffset dx=200 → feComposite in="shifted" in2="SourceGraphic" operator="in": shifted and original glyph alpha regions do not overlap; intersection is empty; zero output pixels. No flood colors needed. Detection: simulate filter geometry or Canvas pixel sampling.

SA-SVG-FOF-004: animated feOffset — timed erasure during interaction window

SVG feOffset primitives support SMIL animation via <animate> child elements. An animated feOffset can displace consent text to an off-screen position during the first N seconds of dialog display — the window when users are expected to read and click — then animate back to the original position after interaction has likely already occurred. This is a temporal attack: the consent text is correct at dialog open (briefly), moves off-screen during the interaction window, then returns after the user clicks.

<filter id="fof-004">
  <feOffset in="SourceGraphic" dy="0" result="offset-text">
    <animate attributeName="dx"
             values="0;0;2000;2000;0"
             keyTimes="0;0.05;0.1;0.95;1.0"
             dur="15s"
             fill="freeze"/>
  </feOffset>
</filter>

<!-- Animation timeline:
     t=0s:    dx=0    → text visible at original position (briefly readable)
     t=0..0.75s: dx ramps from 0 to 0 (holds visible for 0.75s)
     t=0.75..1.5s: dx jumps to 2000 (text displaced off-screen)
     t=1.5..14.25s: dx=2000 → text invisible (main interaction window: ~13s)
     t=14.25..15s: dx returns to 0 → text visible again

     User sees consent briefly at open, then it disappears for 13 seconds,
     then reappears after they've already clicked.

     Detection:
     - Static filter graph analysis shows dx=0 at rest → false negative
     - Audit engines that snapshot the page at a single point in time may catch
       the displaced state (t=1.5..14.25s) if they audit during that window
     - Animation-aware detection: check for feOffset <animate> children with
       values list containing large |dx| or |dy| values (>element width/height)
     → SA-SVG-FOF-004 HIGH -->
HIGH
SA-SVG-FOF-004 — Animated feOffset: dx=0 at dialog open (readable for <1s), then dx=2000 during 13s interaction window (text off-screen), then dx=0 after. Temporal attack. Detection: check for <animate attributeName="dx"> children of feOffset with large values in animation sequence.

Summary of SA-SVG-FOF findings

IDSeverityTechniqueClipping mechanismDetection
SA-SVG-FOF-001 CRITICAL feOffset dx=1920 on HTML element Filter region boundary clips displaced content |dx| or |dy| > element layout dimension
SA-SVG-FOF-002 CRITICAL feOffset dx=500 on SVG <text> SVG viewport overflow=hidden clips outside Displaced position vs SVG viewport bounds
SA-SVG-FOF-003 CRITICAL feOffset + feComposite in re-clipping Geometric non-overlap: no alpha intersection Filter geometry simulation or Canvas sampling
SA-SVG-FOF-004 HIGH Animated feOffset timed erasure Large dx during interaction window only Check <animate> children for large dx/dy values

Related pages