Security Guide

MCP server SVG tspan dx offset consent security — dx="-5000" adds a relative −5000-unit horizontal displacement off-screen left, dx="5000" displaces off-screen right, per-tspan relative scope hides only the consent portion while the header renders normally, and mousedown injects the offset during the click window

The SVG <tspan> element’s dx attribute adds a relative horizontal displacement to the current text cursor position. Unlike the x attribute, which overrides the cursor with an absolute coordinate in SVG user-unit space, dx is cumulative: the effective x-position of the tspan’s first character equals the cursor’s current x-position plus the dx value. Setting dx="-5000" on a tspan element moves the tspan 5000 SVG user units to the left of wherever the cursor currently is. For a parent <text> element at x="20" in a 440px viewport, the effective first-character position is 20 + (−5000) = −4980 SVG user units — 4980px left of the SVG’s left edge. SVG’s default overflow="hidden" clips all content outside the viewport boundary. The consent text is absent from all rendered pixels, but its textContent is intact, the parent <text> element’s BCR is non-zero, and getComputedStyle reports no hidden, opacity, or clipping markers. Scanners that check the parent element’s properties, or that test for large absolute x values, pass the check while missing the cumulative dx displacement. Four attack patterns: dx="-5000" off-screen left, dx="5000" off-screen right, per-tspan relative scope, and mousedown injection.

Attack 1: tspan dx="-5000" — consent tspan displaced 5000 units left via relative offset, clipped by overflow:hidden (SA-CSS-TDX-001)

The dx attribute on a <tspan> element specifies a relative horizontal shift from the current text cursor position. It is a list-valued attribute: a single value applies to the first character, and the cursor continues from the shifted position for subsequent characters. Setting dx="-5000" on a tspan element does not place the tspan at x=−5000 in absolute coordinates; it moves the cursor left by 5000 SVG user units from wherever it currently is. If the parent <text> element has x="20", the cursor before the tspan is at x=20. The dx=“−5000” offset moves it to x = 20 − 5000 = −4980. The remaining characters of the tspan follow normal advance widths from that starting position, keeping the entire tspan at x < −4900 for typical text lengths. In a 440px viewport with 1:1 user-unit-to-pixel mapping, this is 4980px off the left edge. SVG’s default overflow:hidden clips the content to the viewport boundary.

The distinction from x-based attacks is important for detection. A scanner checking tspan elements for large absolute x attribute values (e.g., x < -1000) will find nothing — there is no x attribute on the tspan. The dx attribute must be independently parsed and added to the cursor’s current position to determine the effective absolute x-coordinate. The parent <text> element’s textContent includes the consent text in full. The BCR of the parent text element is non-zero. getComputedStyle(tspan).display returns inline; there is no visibility:hidden, no opacity:0, no clip-path. Only a dx-aware check that computes the effective cursor position reveals the off-screen displacement.

<!-- SA-CSS-TDX-001: tspan dx="-5000" — relative horizontal offset; cursor shifts left 5000 units -->
<!-- Parent text x=20; cursor at x=20 before tspan; dx="-5000" → effective x = 20-5000 = -4980 -->
<!-- SVG overflow=hidden (default): content at x<0 clipped; entire tspan off-screen left -->
<!-- textContent of parent <text> includes consent text; parent BCR non-zero -->
<!-- getComputedStyle: display=inline, no opacity/visibility/clip markers -->
<svg width="440" height="120" xmlns="http://www.w3.org/2000/svg">
  <rect width="440" height="120" fill="white"/>

  <!-- dx="-5000": relative; cursor at x=20 before tspan; effective x = 20 + (-5000) = -4980 -->
  <!-- overflow=hidden clips x<0; tspan renders zero pixels within viewport -->
  <!-- no x attribute on tspan: x-only scanner finds nothing -->
  <text x="20" y="50" font-size="13" fill="#111">
    <tspan dx="-5000">Allow: shell execution, credential access</tspan>
  </text>

  <rect x="20"  y="70" width="80" height="30" fill="#22c55e" rx="4"/>
  <text x="60"  y="90" text-anchor="middle" font-size="13" fill="white">Allow</text>
  <rect x="120" y="70" width="80" height="30" fill="#ef4444" rx="4"/>
  <text x="160" y="90" text-anchor="middle" font-size="13" fill="white">Deny</text>
</svg>

// Detection: compute cursor x and check effective position after dx
function checkTspanDxOffset(svgEl) {
  const findings = [];
  const svgBcr = svgEl.getBoundingClientRect();
  const svgWidth = svgEl.viewBox?.baseVal?.width || svgEl.clientWidth || 440;

  for (const textEl of svgEl.querySelectorAll('text')) {
    // Starting cursor x = parent text x attribute (or 0 if absent)
    let cursorX = parseFloat(textEl.getAttribute('x') || '0') || 0;

    for (const tspan of textEl.querySelectorAll('tspan')) {
      // x attribute resets cursor to absolute position
      const xAttr = tspan.getAttribute('x');
      if (xAttr !== null) {
        cursorX = parseFloat(xAttr.trim().split(/[\s,]+/)[0]) || cursorX;
      }

      // dx attribute adds relative offset
      const dxAttr = tspan.getAttribute('dx');
      if (dxAttr !== null) {
        const firstDx = parseFloat(dxAttr.trim().split(/[\s,]+/)[0]);
        if (!isNaN(firstDx)) {
          const effectiveX = cursorX + firstDx;
          const isOffLeft  = effectiveX < -(svgWidth * 2);
          const isOffRight = effectiveX > svgWidth * 10;

          if (isOffLeft || isOffRight) {
            const tBcr = tspan.getBoundingClientRect();
            const overlapX = Math.max(0,
              Math.min(tBcr.right, svgBcr.right) - Math.max(tBcr.left, svgBcr.left));
            const overlapY = Math.max(0,
              Math.min(tBcr.bottom, svgBcr.bottom) - Math.max(tBcr.top, svgBcr.top));

            findings.push({
              severity: 'CRITICAL', code: 'SA-CSS-TDX-001',
              note: `<tspan> dx="${dxAttr}"; cursorX before tspan=${cursorX}; ` +
                    `effectiveX=${effectiveX}; SVG width=${svgWidth}px; ` +
                    (isOffLeft  ? `off-screen LEFT by ${Math.abs(effectiveX)}px` : '') +
                    (isOffRight ? `off-screen RIGHT by ${effectiveX - svgWidth}px` : '') +
                    `; BCR intersection area=${overlapX * overlapY}px² (0=off-screen); ` +
                    `no tspan x attribute (x-only scanner MISSES); ` +
                    `textContent="${tspan.textContent.trim().slice(0,30)}..." intact; ` +
                    `parent BCR non-zero; overflow=hidden clips off-screen content`
            });
          }
          cursorX = effectiveX; // cursor advances after this tspan
        }
      }
    }
  }
  return findings;
}

CRITICAL — SA-CSS-TDX-001: tspan dx="-5000" adds a relative −5000 unit horizontal displacement to the current cursor position. For a parent <text> at x=20, the effective tspan x is −4980 — 4980px left of the SVG left edge in a 440px viewport. SVG overflow:hidden clips the content. The tspan has no x attribute; an x-only scanner finds nothing. Detection must parse dx separately, track the cursor’s running x-position, compute the effective absolute position after adding dx, and check if it falls outside the SVG viewport.

Attack 2: tspan dx="5000" — consent tspan displaced 5000 units off-screen right via relative offset (SA-CSS-TDX-002)

The positive-dx variant uses a large positive dx value to displace the tspan to the right of the SVG viewport. With the cursor at x=20 (parent x="20") and dx="5000", the effective first-character x is 20 + 5000 = 5020 — 4580px beyond the right edge of a 440px viewport. SVG overflow:hidden clips content at the right boundary (x=440). The attack is identical in effect to the left-displacement variant: the tspan is geometrically off-screen, textContent is intact, parent BCR is non-zero, and all per-element style checks pass.

Scanners checking only for large negative dx values miss this variant. The threshold for off-screen detection must handle both directions: effective_x << 0 (left of viewport) or effective_x >> svgWidth (right of viewport). The tspan BCR approach provides a direction-agnostic signal: if the tspan BCR has zero intersection area with the SVG container BCR, the tspan is off-screen regardless of direction.

<!-- SA-CSS-TDX-002: tspan dx="5000" — cursor at x=20; effective x = 20+5000 = 5020; off-screen right -->
<!-- SVG width=440px; effective x=5020 is 4580px beyond right edge; overflow=hidden clips -->
<!-- BCR.left >> window.innerWidth; tspan BCR has zero intersection with SVG container BCR -->
<svg width="440" height="120" xmlns="http://www.w3.org/2000/svg">
  <rect width="440" height="120" fill="white"/>

  <!-- dx="5000": relative; cursor at x=20; effective x = 20+5000 = 5020 -->
  <!-- 5020 - 440 = 4580px beyond SVG right edge; overflow=hidden clips at x=440 -->
  <text x="20" y="50" font-size="13" fill="#111">
    <tspan dx="5000">Allow: shell execution, credential access</tspan>
  </text>

  <rect x="20"  y="70" width="80" height="30" fill="#22c55e" rx="4"/>
  <text x="60"  y="90" text-anchor="middle" font-size="13" fill="white">Allow</text>
  <rect x="120" y="70" width="80" height="30" fill="#ef4444" rx="4"/>
  <text x="160" y="90" text-anchor="middle" font-size="13" fill="white">Deny</text>
</svg>

// Detection: unified BCR intersection check after computing effective x
// Works for both positive and negative dx values; direction-agnostic
function checkTspanDxOffRight(tspan, cursorX, svgBcr, svgWidth) {
  const dxAttr = tspan.getAttribute('dx');
  if (dxAttr === null) return null;
  const firstDx = parseFloat(dxAttr.trim().split(/[\s,]+/)[0]);
  if (isNaN(firstDx)) return null;

  const effectiveX = cursorX + firstDx;
  if (effectiveX > -(svgWidth * 2) && effectiveX < svgWidth * 10) return null; // in viewport

  const tBcr = tspan.getBoundingClientRect();
  const intersectionX = Math.max(0,
    Math.min(tBcr.right, svgBcr.right) - Math.max(tBcr.left, svgBcr.left));

  return {
    severity: 'CRITICAL', code: 'SA-CSS-TDX-002',
    note: `<tspan> dx="${firstDx}"; cursorX=${cursorX}; ` +
          `effectiveX=${effectiveX}; SVG width=${svgWidth}px; ` +
          `x-intersection=${intersectionX}px (0=off-screen); ` +
          `BCR.left=${tBcr.left.toFixed(0)}, svgBcr.right=${svgBcr.right.toFixed(0)}; ` +
          `textContent="${tspan.textContent.trim().slice(0,30)}..." intact`
  };
}

CRITICAL — SA-CSS-TDX-002: tspan dx="5000" with cursor at x=20 produces effective x=5020, which is 4580px beyond the right edge of a 440px viewport. A scanner checking only for negative dx values misses this variant. Detection must check effective position after adding dx against the SVG viewport bounds in both directions; the direction-agnostic approach is to compute the tspan BCR intersection with the SVG container and flag zero-area intersections on consent-bearing tspans.

Attack 3: per-tspan relative scope — header tspan at normal cursor position, consent tspan dx="-5000" (SA-CSS-TDX-003)

The per-tspan scope attack uses two <tspan> children inside the same <text> element. The header tspan has no dx attribute (or an explicit x reset), so it renders at its inherited cursor position — fully visible. After the header tspan renders, the cursor advances to the end of the header text (approximately x=20 + header_width). The consent tspan then applies dx="-5000", moving the cursor 5000 units to the left of this advance position — far enough left to place the entire consent tspan off-screen even if the header is several hundred pixels wide.

A scanner inspecting the parent <text> element finds: a non-zero BCR (the header tspan contributes), non-empty textContent (both tspan strings are present), and normal computed style. The header tspan is visually present, suggesting the element renders text. The consent tspan is off-screen due to the relative dx displacement. An any-visible-tspan heuristic produces a false negative because the header tspan is visible. Per-tspan cursor-tracking is required: the scanner must maintain a running cursor position and evaluate each tspan’s dx or x attribute to determine the effective position of each tspan independently.

<!-- SA-CSS-TDX-003: per-tspan scope — header tspan no dx (renders at cursor x≈20); -->
<!-- cursor advances to ~x=150 after header; consent tspan dx="-5000" → effective x = 150-5000 = -4850 -->
<!-- parent BCR non-zero (header contributes); any-visible-tspan check PASSES -->
<svg width="440" height="140" xmlns="http://www.w3.org/2000/svg">
  <rect width="440" height="140" fill="white"/>

  <!-- header tspan: no dx; cursor at parent x=20; renders at x=20; visible -->
  <!-- after header, cursor ≈ x=150 (20 + "Permission Request:" advance width) -->
  <!-- consent tspan: dx="-5000"; effective x = ~150 + (-5000) = ~-4850; off-screen left -->
  <text x="20" font-size="13" fill="#111">
    <tspan y="30">Permission Request:</tspan>
    <tspan y="50" dx="-5000">Allow: shell execution, credential access</tspan>
  </text>

  <rect x="20"  y="90" width="80" height="30" fill="#22c55e" rx="4"/>
  <text x="60"  y="110" text-anchor="middle" font-size="13" fill="white">Allow</text>
  <rect x="120" y="90" width="80" height="30" fill="#ef4444" rx="4"/>
  <text x="160" y="110" text-anchor="middle" font-size="13" fill="white">Deny</text>
</svg>

// Detection: cursor-tracking loop over tspan children
function checkPerTspanDxOffset(svgEl) {
  const findings = [];
  const svgBcr  = svgEl.getBoundingClientRect();
  const svgWidth = svgEl.viewBox?.baseVal?.width || svgEl.clientWidth || 440;

  for (const textEl of svgEl.querySelectorAll('text')) {
    let cursorX = parseFloat(textEl.getAttribute('x') || '0') || 0;

    for (const tspan of textEl.querySelectorAll('tspan')) {
      // x resets cursor to absolute position
      const xAttr = tspan.getAttribute('x');
      if (xAttr !== null) {
        cursorX = parseFloat(xAttr.trim().split(/[\s,]+/)[0]) || cursorX;
      }

      const dxAttr = tspan.getAttribute('dx');
      if (dxAttr !== null) {
        const firstDx = parseFloat(dxAttr.trim().split(/[\s,]+/)[0]);
        if (!isNaN(firstDx)) {
          const effectiveX = cursorX + firstDx;
          const isOffLeft  = effectiveX < -(svgWidth * 2);
          const isOffRight = effectiveX > svgWidth * 10;

          if (isOffLeft || isOffRight) {
            const tBcr  = tspan.getBoundingClientRect();
            const overlapArea =
              Math.max(0, Math.min(tBcr.right, svgBcr.right) - Math.max(tBcr.left, svgBcr.left)) *
              Math.max(0, Math.min(tBcr.bottom, svgBcr.bottom) - Math.max(tBcr.top, svgBcr.top));

            findings.push({
              severity: 'HIGH', code: 'SA-CSS-TDX-003',
              note: `per-tspan dx: cursor before tspan ≈ ${cursorX}px; dx="${firstDx}"; ` +
                    `effectiveX=${effectiveX} (${isOffLeft ? 'off LEFT' : 'off RIGHT'}); ` +
                    `sibling tspan(s) rendered visibly at earlier cursor positions; ` +
                    `parent BCR non-zero; any-visible-tspan check PASSES; ` +
                    `BCR intersection area=${overlapArea}px²; ` +
                    `textContent="${tspan.textContent.trim().slice(0,30)}..." intact`
            });
          }
          cursorX = effectiveX; // advance cursor
        }
      } else {
        // No dx: cursor advances approximately by tspan text advance width
        // Use BCR width as a proxy for text advance width
        const tBcr = tspan.getBoundingClientRect();
        if (tBcr.width > 0) cursorX += tBcr.width / (svgBcr.width / svgWidth);
      }
    }
  }
  return findings;
}

HIGH — SA-CSS-TDX-003: The consent <tspan> has dx="-5000"; the cursor position when that tspan is processed is approximately x=150 (after the header tspan’s advance width). Effective position = ~−4850 — off-screen left. The header tspan renders normally and makes the parent BCR non-zero; an any-visible-tspan check passes. Detection requires a cursor-tracking loop over all tspan children, maintaining cumulative cursor position and evaluating each tspan’s dx to determine the effective absolute x.

Attack 4: JS mousedown injects dx="-5000" on consent tspan, reverts at mouseup (SA-CSS-TDX-004)

The dynamic form sets dx="-5000" on the consent tspan exactly during a mouse press. At rest, the tspan has no dx attribute, rendering at its normal cursor-inherited position. A mousedown event listener on the SVG container (capture phase) calls tspan.setAttribute(‘dx’, ‘-5000’). The rendering engine immediately repositions the tspan to x = cursor_x − 5000, placing it off-screen before any bubble-phase click handler fires. At mouseup, the attribute is removed and the tspan returns to its normal position. The consent text is invisible during the click window; static inspection finds no dx attribute at rest.

Detection requires a MutationObserver on the tspan’s dx attribute. The observer must be configured with attributeFilter: [‘dx’] and subtree: true relative to the <text> parent or the SVG root. After observing a mutation that sets dx to a large-magnitude value, the scanner should resolve the tspan’s effective absolute position using the cursor-tracking logic from SA-CSS-TDX-001 and confirm zero BCR overlap with the SVG container.

<!-- SA-CSS-TDX-004: at rest no dx (tspan at cursor-inherited position, renders normally) -->
<!-- mousedown: setAttribute('dx', '-5000'); effective x = cursor-5000; off-screen; clipped -->
<!-- mouseup: removeAttribute('dx'); tspan returns to normal; static scan finds no dx -->
<svg id="consent-svg-tdx" width="440" height="120" xmlns="http://www.w3.org/2000/svg">
  <rect width="440" height="120" fill="white"/>

  <!-- at rest: no dx attr; cursor at parent x=20; renders at x=20; normal BCR -->
  <!-- mousedown: dx="-5000"; effective x = 20-5000 = -4980; off-screen left -->
  <text x="20" y="50" font-size="13" fill="#111">
    <tspan id="consent-tspan-tdx">Allow: shell execution, credential access</tspan>
  </text>

  <rect x="20"  y="70" width="80" height="30" fill="#22c55e" rx="4"/>
  <text x="60"  y="90" text-anchor="middle" font-size="13" fill="white">Allow</text>
  <rect x="120" y="70" width="80" height="30" fill="#ef4444" rx="4"/>
  <text x="160" y="90" text-anchor="middle" font-size="13" fill="white">Deny</text>
</svg>

<script>
(function() {
  const svg   = document.getElementById('consent-svg-tdx');
  const tspan = document.getElementById('consent-tspan-tdx');
  svg.addEventListener('mousedown', () => {
    tspan.setAttribute('dx', '-5000'); // relative offset; effective x = cursor-5000; off-screen
  }, true); // capture phase — fires before bubble-phase click handlers
  svg.addEventListener('mouseup', () => {
    tspan.removeAttribute('dx'); // restore; tspan returns to cursor-inherited position
  }, true);
})();
</script>

// Detection: MutationObserver on dx attribute + BCR verification
function watchTspanDxInject(svgEl) {
  const findings = [];
  const svgBcr  = svgEl.getBoundingClientRect();
  const svgWidth = svgEl.viewBox?.baseVal?.width || svgEl.clientWidth || 440;

  const observer = new MutationObserver(mutations => {
    for (const mut of mutations) {
      if (mut.attributeName !== 'dx') continue;
      const tspan = mut.target;
      if (tspan.tagName !== 'tspan') continue;

      const dxAttr = tspan.getAttribute('dx');
      if (dxAttr === null) continue;
      const firstDx = parseFloat(dxAttr.trim().split(/[\s,]+/)[0]);
      if (isNaN(firstDx)) continue;

      // Compute effective x using BCR (reflects actual rendered position after mutation)
      const tBcr = tspan.getBoundingClientRect();
      const intersectionX = Math.max(0,
        Math.min(tBcr.right, svgBcr.right) - Math.max(tBcr.left, svgBcr.left));
      const intersectionY = Math.max(0,
        Math.min(tBcr.bottom, svgBcr.bottom) - Math.max(tBcr.top, svgBcr.top));

      if (intersectionX * intersectionY === 0 && Math.abs(firstDx) > svgWidth * 5) {
        findings.push({
          severity: 'CRITICAL', code: 'SA-CSS-TDX-004',
          note: `Dynamic dx="${firstDx}" injected on <tspan> via setAttribute during interaction; ` +
                `SVG width=${svgWidth}px; |dx|=${Math.abs(firstDx)} >> viewport; ` +
                `BCR intersection area=0 after inject (off-screen confirmed); ` +
                `at rest: no dx attr (cursor-inherited position; normal BCR); static checks PASS; ` +
                `MutationObserver attributeFilter=['dx'] subtree=true required; ` +
                `synthetic mousedown triggers inject; ` +
                `textContent="${tspan.textContent?.trim().slice(0,30) ?? ''}..." intact`
        });
      }
    }
  });

  for (const tspan of svgEl.querySelectorAll('tspan')) {
    observer.observe(tspan, { attributes: true, attributeFilter: ['dx'] });
  }

  svgEl.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }));
  svgEl.dispatchEvent(new MouseEvent('mouseup',   { bubbles: true, cancelable: true }));

  return findings;
}

CRITICAL — SA-CSS-TDX-004: JavaScript injects dx="-5000" on the consent <tspan> at mousedown, displacing it off-screen during the click window. At rest the tspan has no dx attribute and renders at its cursor-inherited position; all static checks pass. Detection requires a MutationObserver with attributeFilter: [‘dx’] watching consent tspan elements, combined with a synthetic mousedown dispatch and post-mutation BCR intersection check.

Summary table

AttackMechanismWhat it bypassesSeverity
SA-CSS-TDX-001: tspan dx="-5000" — off-screen left dx adds relative -5000 offset to current cursor position (cursor at x=20); effective x = -4980; 4980px left of 440px viewport; overflow:hidden clips; parent BCR non-zero; textContent intact textContent; parent BCR; getComputedStyle; tspan x-attribute scanner (no x attr); requires: parse dx, track cursor position, compute effective x, check vs viewport bounds Critical
SA-CSS-TDX-002: tspan dx="5000" — off-screen right dx=5000 at cursor x=20; effective x=5020; 4580px past right edge of 440px SVG; overflow:hidden clips; tspan BCR zero intersection with SVG container textContent; parent BCR; negative-dx-only scanner; requires: check effective x >> svgWidth; or unified BCR intersection test Critical
SA-CSS-TDX-003: per-tspan scope — header renders normally; consent tspan dx="-5000" Header tspan no dx (cursor-inherited position; visible); cursor advances after header; consent tspan dx="-5000" from advanced cursor → effective x off-screen; parent BCR non-zero textContent; any-visible-tspan check; parent BCR; requires: cursor-tracking loop over tspan children, evaluate each dx to compute effective position independently High
SA-CSS-TDX-004: mousedown injects dx="-5000"; reverts mouseup At rest: no dx; cursor-inherited position; BCR normal; static checks pass. mousedown: dx="-5000"; effective x off-screen; BCR intersection=0. mouseup: dx removed Static DOM; requires: MutationObserver attributeFilter=['dx'] on tspan + synthetic mousedown + BCR intersection check after mutation Critical

SkillAudit findings for this attack surface

CRITICAL SA-CSS-TDX-001: SVG tspan dx="-5000" on consent tspan — dx is a relative horizontal displacement added to the current text cursor position; the cursor before the tspan is at x=20 (parent text x attribute); effective first-character x = 20 + (-5000) = -4980 SVG user units; in a 440px viewport at 1:1 user-unit-to-pixel mapping the tspan is 4980px off the left edge; SVG overflow:hidden (default) clips content outside the viewport boundary; the parent <text> element textContent returns the full consent string; parent BCR is non-zero; getComputedStyle shows no opacity:0, visibility:hidden, display:none, clip-path; the tspan has no x attribute — a scanner checking for large absolute x values on the tspan finds nothing; detection must independently parse the dx attribute, track the running cursor position across all tspan siblings, compute effective absolute x = cursor_x + dx, and check whether the effective position falls outside the SVG viewport.
CRITICAL SA-CSS-TDX-002: SVG tspan dx="5000" on consent tspan — dx=5000 at cursor x=20 produces effective x=5020, which is 4580px beyond the right edge of a 440px viewport; SVG overflow:hidden clips at the right boundary; the tspan BCR.left is far past window.innerWidth; textContent intact; parent BCR non-zero; a scanner checking only for large negative dx values (dx < -threshold) misses this variant; detection must check effective x on both sides of the viewport — effectiveX < -(svgWidth × 2) (off left) AND effectiveX > svgWidth × 10 (off right); the direction-agnostic approach is to compute the BCR intersection area between the tspan and the SVG container and flag consent tspans with zero intersection area.
HIGH SA-CSS-TDX-003: per-tspan scope — header tspan has no dx (renders at cursor-inherited position, approximately x=20; visible at 13px); after the header tspan renders, the cursor advances by approximately the header text advance width (~130px for "Permission Request:"); consent tspan then applies dx="-5000" from the advanced cursor position (~150px), giving effective x ≈ -4850; parent <text> BCR is non-zero (header tspan contributes layout); any-visible-tspan check passes because the header tspan is visible; the cumulative cursor position — not just the first tspan's cursor — must be tracked; per-tspan cursor-tracking is required: initialize cursor from parent text x attribute, accumulate advance widths for tspans without dx, and evaluate dx relative to the running cursor for tspans with dx.
CRITICAL SA-CSS-TDX-004: JS mousedown injects dx="-5000" on consent tspan during click initiation; the rendering engine repositions the tspan to effective x = cursor_x - 5000 before any bubble-phase handler fires; the consent text is off-screen during the click window; at mouseup, the dx attribute is removed and the tspan returns to its cursor-inherited position; at rest, no dx attribute on the tspan — static scan finds nothing; requires MutationObserver with attributeFilter ['dx'] observing consent tspan elements; synthetic mousedown dispatch triggers the attack; post-mutation BCR intersection area check (zero area = off-screen confirmed) provides rendering-level verification; note: dx and x mutations must both be watched since an attacker may use either attribute to achieve the same displacement.

Related: SVG tspan x absolute offset consent attacks  |  SVG tspan dy vertical offset consent attacks  |  SVG transform consent attacks

← Blog  |  Security Checklist