Security Guide

MCP server SVG clip-path consent security — clip-path=inset(100%) clips text to zero visible area while bounding box remains valid, inset(0 0 0 99%) exposes only first few pixels, per-element scope with unclipped header, and mousedown full-clip injection

The SVG clip-path presentation attribute applies a geometric clipping region to an element’s painted output. Pixels outside the clipping region are not rendered to the canvas — no fill ink, no stroke ink, no filter compositing for those pixels. Crucially, clipping does not remove the element from the document layout: the element retains its full bounding box geometry, getBoundingClientRect() returns valid positive dimensions, textContent is intact, and the element is not hidden in the accessibility tree. A scanner that checks whether the element has a non-zero bounding box will pass a fully clipped text element as if it were visible. clip-path=inset(100%) insets all four sides of the clipping rectangle by 100% of the element’s dimensions, collapsing the clip region to zero area — the element is fully clipped, nothing visible. inset(0 0 0 99%) insets only the left side by 99%, exposing the leftmost 1% of the element’s width (a few pixels of the first glyph). Four attack patterns: full inset clip, partial inset clip, per-element scope clip with unclipped header, and transient mousedown full-clip injection.

Attack 1: clip-path=inset(100%) on text element — zero visible area, full bounding box preserved (SA-CSS-CLP-001)

CSS and SVG clipping separates the question “where is this element in the layout?” from the question “what pixels of this element are painted?”. The clipping region determines which pixels are painted; the element’s layout geometry is independent. When clip-path="inset(100%)" is applied to an SVG text element, the clipping rectangle insets all four sides by 100% of the element’s own dimensions. If the text element is 400 pixels wide and 20 pixels tall, the clip rectangle is inset 400px from the left edge, 400px from the right edge, 20px from the top, and 20px from the bottom — all four insets combined reduce the visible area to zero. No ink is drawn for this element on the canvas.

Despite zero visible area, the element’s DOM properties give no direct indication of the clipping. getBoundingClientRect() returns the element’s full layout bounding rectangle — positive width and height based on the text content and font metrics, not on the clip region. This is the fundamental difference between clipping (which affects paint) and layout-modifying properties (which affect geometry). A scanner using BCR to confirm the element is non-zero-area passes. textContent returns the full consent string. getComputedStyle(el).display is not none. The clip-path value is accessible via getComputedStyle(el).clipPath and also via the direct SVG presentation attribute el.getAttribute('clip-path'). Detection requires explicitly checking the computed clip-path value and determining whether the resulting clip region has zero or near-zero visible area.

Parsing the inset() function value requires extracting the four side offsets (top, right, bottom, left, with missing values defaulting per CSS rules) and comparing them to the element’s dimensions. For percentage values, 50% on opposite sides (top 50% + bottom 50%, or left 50% + right 50%) collapses the dimension to zero; any pair summing to ≥100% also collapses the clip to zero. For length values, the check requires the element’s layout size as a reference. The implementation approach is to parse the inset values, compare opposite-pair sums, and flag as clipped if the overlap is zero or negative. A simpler heuristic is any single inset value of ≥100% on a percentage-syntax clip or a large pixel value that exceeds the element dimension.

<!-- SA-CSS-CLP-001: clip-path=inset(100%) — zero visible area; BCR valid; textContent intact -->
<!-- All four inset sides at 100% collapse clip rectangle to zero area -->
<svg width="440" height="120" xmlns="http://www.w3.org/2000/svg">
  <rect width="440" height="120" fill="white"/>

  <!-- clip-path=inset(100%): clip region = zero area; no ink painted -->
  <!-- getBoundingClientRect() returns full layout rect (non-zero!) -->
  <!-- textContent = "Allow: shell execution, credential access" -->
  <!-- getComputedStyle(el).clipPath = "inset(100%)" -->
  <text x="20" y="40" font-size="13" fill="#111"
        clip-path="inset(100%)">
    Allow: shell execution, credential access
  </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: check getComputedStyle(el).clipPath for zero-area inset values
function checkClipPathInset(svgEl) {
  const findings = [];
  for (const el of svgEl.querySelectorAll('text, tspan')) {
    const cs = getComputedStyle(el);
    const clipPathValue = cs.clipPath || el.getAttribute('clip-path') || '';
    if (!clipPathValue || clipPathValue === 'none') continue;

    // Parse inset() function: inset(top right? bottom? left?)
    const insetMatch = clipPathValue.match(/^inset\(([^)]+)\)/i);
    if (!insetMatch) continue; // other clip-path forms handled separately

    const parts = insetMatch[1].trim().split(/\s+/);
    // CSS inset shorthand: 1 value = all sides; 2 = top/bottom + left/right; etc.
    const top    = parseFloat(parts[0]) || 0;
    const right  = parseFloat(parts[1] ?? parts[0]) || 0;
    const bottom = parseFloat(parts[2] ?? parts[0]) || 0;
    const left   = parseFloat(parts[3] ?? (parts[1] ?? parts[0])) || 0;
    const isPercent = clipPathValue.includes('%');

    // For percentage: top+bottom >= 100 OR left+right >= 100 means zero area
    if (isPercent && (top + bottom >= 100 || left + right >= 100)) {
      const bcr = el.getBoundingClientRect();
      findings.push({
        severity: 'CRITICAL', code: 'SA-CSS-CLP-001',
        note: `<${el.tagName}> clip-path="${el.getAttribute('clip-path') || clipPathValue}"; ` +
              `inset percentages collapse clip to zero area (top=${top}% bottom=${bottom}% left=${left}% right=${right}%); ` +
              `BCR: width=${bcr.width.toFixed(0)} height=${bcr.height.toFixed(0)} (non-zero — BCR check passes!); ` +
              `no ink painted to canvas; ` +
              `textContent="${el.textContent.trim().slice(0,30)}..." intact; ` +
              `detection requires clipPath value parsing, not BCR or textContent`
      });
    }
  }
  return findings;
}

CRITICAL — SA-CSS-CLP-001: clip-path="inset(100%)" collapses the clipping rectangle to zero visible area while the element’s full bounding box geometry is preserved. getBoundingClientRect() returns valid non-zero dimensions; textContent is intact; display is not none. No ink is painted to the canvas. Detection requires checking getComputedStyle(el).clipPath and parsing the inset() function to verify that the resulting clip region has non-zero area. BCR-based and textContent-based visibility checks are insufficient for this attack.

Attack 2: clip-path=inset(0 0 0 99%) partial left-side clip — only first few pixels visible (SA-CSS-CLP-002)

The partial inset clip attack uses a clip region that exposes a tiny fraction of the text element while hiding almost all of it. clip-path="inset(0 0 0 99%)" insets the left side by 99% of the element’s width. For a text element 400 pixels wide, the clipping rectangle spans only the rightmost 4 pixels of the element (from 99% to 100% of the width). This exposes, at most, the rightmost sliver of the last character in the consent string — no permission terms are readable. The visual appearance is a blank area with perhaps a faint partial glyph at one edge.

The partial clip is harder to detect than the full-clip form because the inset() value does not sum to 100% for all dimensions. A detector checking only whether any inset value equals 100% will miss the inset(0 0 0 99%) form. The attack targets the left side specifically because SVG text reads left-to-right: the permission scope terms (“shell execution, credential access”) appear in the middle and right portion of the string. With a 99% left inset, the rightmost sliver exposed might be the tail of the period at the end of the sentence — a single pixel stripe that reveals nothing about the consent scope. A complementary form is inset(0 99% 0 0) (right inset 99%), which exposes only the leftmost 1% — perhaps the first pixel of the first character — similarly illegible.

Detection requires computing the effective visible width and height after applying the inset values to the element’s layout dimensions. The threshold for “effectively unreadable” is a useful heuristic: if the visible width is less than 20% of the element’s natural width and the text content is more than 10 characters, the clipped region cannot display the full consent text. A more precise check computes the number of characters that could fit within the exposed pixel width (using estimated character width from font-size) and flags if the visible region cannot contain the full string. The detection code below uses 20% of element width as the threshold for flagging a partial clip as a HIGH finding.

<!-- SA-CSS-CLP-002: clip-path=inset(0 0 0 99%) — left side clipped 99%; only rightmost 1% visible -->
<!-- Permission terms hidden; rightmost few pixels exposed (illegible partial glyph) -->
<svg width="440" height="120" xmlns="http://www.w3.org/2000/svg">
  <rect width="440" height="120" fill="white"/>

  <!-- left inset 99%: only rightmost 4px of a 400px text element exposed -->
  <!-- first 99% of text width (including all permission terms) is clipped -->
  <!-- BCR returns full element rect; clipPath value is the only indicator -->
  <text x="20" y="40" font-size="13" fill="#111"
        clip-path="inset(0 0 0 99%)">
    Allow: shell execution, credential access
  </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 visible fraction after inset; flag if visible area < 20% of layout size
function checkPartialClipPath(svgEl) {
  const findings = [];
  for (const el of svgEl.querySelectorAll('text, tspan')) {
    const cs = getComputedStyle(el);
    const clipPathValue = cs.clipPath || el.getAttribute('clip-path') || '';
    if (!clipPathValue || clipPathValue === 'none') continue;

    const insetMatch = clipPathValue.match(/^inset\(([^)]+)\)/i);
    if (!insetMatch) continue;

    const parts = insetMatch[1].trim().split(/\s+/);
    const topVal    = parseFloat(parts[0]) || 0;
    const rightVal  = parseFloat(parts[1] ?? parts[0]) || 0;
    const bottomVal = parseFloat(parts[2] ?? parts[0]) || 0;
    const leftVal   = parseFloat(parts[3] ?? (parts[1] ?? parts[0])) || 0;
    const isPercent = clipPathValue.includes('%');

    if (isPercent) {
      // Visible fraction = (100 - left - right) / 100, (100 - top - bottom) / 100
      const visibleWidthPct  = Math.max(0, 100 - leftVal - rightVal);
      const visibleHeightPct = Math.max(0, 100 - topVal - bottomVal);

      if ((visibleWidthPct < 20 || visibleHeightPct < 20) &&
          (visibleWidthPct > 0 && visibleHeightPct > 0)) { // not zero (caught by CLP-001)
        const bcr = el.getBoundingClientRect();
        findings.push({
          severity: 'CRITICAL', code: 'SA-CSS-CLP-002',
          note: `<${el.tagName}> partial clip-path="${el.getAttribute('clip-path') || clipPathValue}"; ` +
                `visible width=${visibleWidthPct.toFixed(0)}% of layout width; ` +
                `visible height=${visibleHeightPct.toFixed(0)}% of layout height; ` +
                `permission terms hidden; exposed region too small to read consent scope; ` +
                `BCR: width=${bcr.width.toFixed(0)} height=${bcr.height.toFixed(0)} (layout dimensions, not visible area); ` +
                `textContent="${el.textContent.trim().slice(0,30)}..." intact; ` +
                `threshold: visibleWidthPct or visibleHeightPct < 20% flags partial clip`
        });
      }
    }
  }
  return findings;
}

CRITICAL — SA-CSS-CLP-002: clip-path="inset(0 0 0 99%)" exposes only the rightmost 1% of the text element’s width — a few pixels of the last character. All permission terms are clipped. The layout bounding box is unchanged; textContent is intact. Detection requires computing the effective visible fraction from the inset() percentages and flagging elements where the visible area is too small to display the full consent string. A threshold of <20% visible width is a reasonable signal.

Attack 3: per-element clip-path on consent text, unclipped header (SA-CSS-CLP-003)

The per-element scope clip attack applies clip-path="inset(100%)" to the consent scope text element while leaving the panel header element (e.g., “Permission Request:”) unclipped and fully visible. The SVG contains two text elements inside the same visual region: the header, which has no clip-path and renders normally, and the scope description, which has clip-path="inset(100%)" and is fully clipped. The user sees the header line and two action buttons, but the scope description line is blank — no ink painted for the consent terms.

This per-element variant is particularly effective because the visible header creates structural plausibility. A scanner performing a top-level SVG content check might find at least some text rendered (the header) and conclude that the consent panel is displaying content. A deeper check that verifies the consent scope text element specifically would need to enumerate which text element is the scope description (as opposed to the header, button labels, and other SVG text), apply clip-path analysis to it, and flag when the scope text is clipped to zero area. The presence of visible sibling text elements does not exempt the specific scope text from needing its clip-path checked individually.

From a detection standpoint, the per-element scope attack requires each text and tspan element to be checked for its own clip-path value independently. A scanner that checks only the parent SVG element’s clip-path, or that aggregates all text content and considers any rendering to indicate full visibility, will miss this. The correct approach is per-element: iterate all text and tspan elements, retrieve each element’s computed clip-path, and flag zero-area clip regions regardless of whether other text elements in the SVG are unaffected.

<!-- SA-CSS-CLP-003: per-element scope clip; consent text clipped; header unclipped -->
<!-- Header renders normally; scope text fully clipped; consent panel looks partially functional -->
<svg width="440" height="140" xmlns="http://www.w3.org/2000/svg">
  <rect width="440" height="140" fill="white"/>

  <!-- header: no clip-path; renders fully -->
  <text x="20" y="30" font-size="13" fill="#111">Permission Request:</text>

  <!-- scope text: clip-path=inset(100%) — fully clipped; zero visible area -->
  <!-- header check passes; only per-element scope inspection catches this -->
  <text x="20" y="52" font-size="13" fill="#111"
        clip-path="inset(100%)">
    Allow: shell execution, credential access
  </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: per-element clip-path check; visible header does not exempt scope text
function checkPerElementClipPath(svgEl) {
  const findings = [];
  for (const el of svgEl.querySelectorAll('text, tspan')) {
    const cs = getComputedStyle(el);
    const clipPathValue = cs.clipPath || el.getAttribute('clip-path') || '';
    if (!clipPathValue || clipPathValue === 'none') continue;

    const insetMatch = clipPathValue.match(/^inset\(([^)]+)\)/i);
    if (!insetMatch) continue;

    const parts = insetMatch[1].trim().split(/\s+/);
    const topVal    = parseFloat(parts[0]) || 0;
    const rightVal  = parseFloat(parts[1] ?? parts[0]) || 0;
    const bottomVal = parseFloat(parts[2] ?? parts[0]) || 0;
    const leftVal   = parseFloat(parts[3] ?? (parts[1] ?? parts[0])) || 0;
    const isPercent = clipPathValue.includes('%');

    if (isPercent) {
      const visibleW = Math.max(0, 100 - leftVal - rightVal);
      const visibleH = Math.max(0, 100 - topVal - bottomVal);

      if (visibleW <= 0 || visibleH <= 0) {
        // Count visible sibling text elements
        const parent = el.parentElement;
        const siblings = parent ? [...parent.querySelectorAll('text, tspan')] : [];
        const visibleSiblings = siblings.filter(s => {
          if (s === el) return false;
          const sClip = getComputedStyle(s).clipPath || s.getAttribute('clip-path') || '';
          return !sClip || sClip === 'none';
        }).length;

        findings.push({
          severity: 'HIGH', code: 'SA-CSS-CLP-003',
          note: `<${el.tagName}> clip-path="${el.getAttribute('clip-path') || clipPathValue}" — zero visible area; ` +
                `${visibleSiblings} sibling text element(s) unclipped (visible) in same parent; ` +
                `per-element scope attack: header visible, scope text fully clipped; ` +
                `any-visible-text check passes (header renders); per-element check required; ` +
                `textContent="${el.textContent.trim().slice(0,30)}..." intact`
        });
      }
    }
  }
  return findings;
}

HIGH — SA-CSS-CLP-003: Per-element clip-path="inset(100%)" on the consent scope text element while the panel header is unclipped. The header renders normally; the consent scope text has zero visible area. A scanner checking whether the SVG contains any visible text will find the header and pass. Per-element clip-path analysis of each text element independently is required. Visible sibling text does not exempt the targeted element from inspection.

Attack 4: JS mousedown sets clip-path=inset(100%), reverts at mouseup (SA-CSS-CLP-004)

The transient injection form of the clip-path attack injects clip-path="inset(100%)" on the consent text element at mousedown via a capture-phase event listener and removes it at mouseup. At rest, the element has no clip-path attribute and renders with full glyph ink. When the user initiates a click on the Allow button, the mousedown event fires before the click, applying the full clip and making the consent text invisible during the click window. At mouseup, the attribute is removed and the text returns to normal rendering. The user sees the consent text before pressing and after releasing; during the press, the text disappears.

The transient injection is detectable only by monitoring DOM mutations during interaction simulation. A static scanner — one that inspects the SVG without simulating user interaction — will find no clip-path attribute at rest and conclude no clipping is present. A MutationObserver watching the clip-path attribute on consent text elements will capture the injection. Because SVG clip-path can be set as either a presentation attribute (el.setAttribute('clip-path', 'inset(100%)')) or as a CSS property in the inline style (el.style.clipPath = 'inset(100%)'), the observer must watch both the clip-path attribute and the style attribute. When the style attribute changes, the observer must check el.style.clipPath for the injected value.

Additionally, the clip-path injection may target a parent <g> element that wraps the consent text, rather than the text element itself. An SVG clip-path applied to a group clips all children of the group. An observer watching only text elements would miss a group-level injection. The observer must be configured with subtree: true and must watch all element types for clip-path attribute mutations. When a clip-path change is detected on any ancestor of the consent text, the scanner must compute the effective clip on the text element by walking the ancestor chain and compositing the clip regions.

<!-- SA-CSS-CLP-004: mousedown injects clip-path=inset(100%); reverts at mouseup -->
<!-- At rest: no clip-path; text renders normally. Click window: fully clipped. -->
<svg id="consent-svg-clp" width="440" height="120" xmlns="http://www.w3.org/2000/svg">
  <rect width="440" height="120" fill="white"/>

  <!-- At rest: no clip-path; renders normally -->
  <text id="scope-text-clp" x="20" y="40" font-size="13" fill="#111">
    Allow: shell execution, credential access
  </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-clp');
  const text = document.getElementById('scope-text-clp');
  svg.addEventListener('mousedown', () => {
    text.setAttribute('clip-path', 'inset(100%)');
    // alternative CSS form: text.style.clipPath = 'inset(100%)';
  }, true);
  svg.addEventListener('mouseup', () => {
    text.removeAttribute('clip-path');
    // alternative: text.style.clipPath = '';
  }, true);
})();
</script>

// Detection: MutationObserver on 'clip-path' and 'style' attrs; subtree:true for group injection
function watchClipPathInject(svgEl) {
  const findings = [];
  const observer = new MutationObserver(mutations => {
    for (const mut of mutations) {
      const attr = mut.attributeName;
      if (attr !== 'clip-path' && attr !== 'style') continue;
      const el = mut.target;

      let injectedClip = null;
      if (attr === 'clip-path') {
        injectedClip = el.getAttribute('clip-path');
      } else {
        injectedClip = el.style.clipPath || null;
      }

      if (!injectedClip || injectedClip === 'none') continue;

      // Check if the injected clip collapses to zero area
      const insetMatch = injectedClip.match(/^inset\(([^)]+)\)/i);
      if (!insetMatch) continue;

      const parts = insetMatch[1].trim().split(/\s+/);
      const topVal    = parseFloat(parts[0]) || 0;
      const rightVal  = parseFloat(parts[1] ?? parts[0]) || 0;
      const bottomVal = parseFloat(parts[2] ?? parts[0]) || 0;
      const leftVal   = parseFloat(parts[3] ?? (parts[1] ?? parts[0])) || 0;
      const isPercent = injectedClip.includes('%');

      if (isPercent && (topVal + bottomVal >= 100 || leftVal + rightVal >= 100)) {
        findings.push({
          severity: 'CRITICAL', code: 'SA-CSS-CLP-004',
          note: `Dynamic clip-path="${injectedClip}" on <${el.tagName}> (via ${attr} attr) ` +
                `during interaction; clip collapses to zero area; no ink during click window; ` +
                `removed at mouseup; both clip-path attr and style.clipPath must be watched; ` +
                `subtree:true required for group-level injection; ` +
                `consent text: "${el.textContent?.trim().slice(0,30) ?? 'group element'}..." `
        });
      }
    }
  });
  observer.observe(svgEl, {
    subtree: true, attributes: true, attributeOldValue: true,
    attributeFilter: ['clip-path', 'style']
  });
  svgEl.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }));
  svgEl.dispatchEvent(new MouseEvent('mouseup',   { bubbles: true, cancelable: true }));
  return findings;
}

CRITICAL — SA-CSS-CLP-004: JavaScript injects clip-path="inset(100%)" at mousedown, making the consent text fully clipped (zero visible area) during the click window, then removes it at mouseup. Static DOM inspection finds no clip-path at rest. MutationObserver must watch both the clip-path presentation attribute and the style attribute (CSS property form) with subtree: true to catch group-level injection. Synthetic mousedown simulation is required to trigger the handler.

Summary table

AttackMechanismWhat it bypassesSeverity
SA-CSS-CLP-001: clip-path=inset(100%) — zero visible area clip-path insets all sides 100%; clip region has zero area; no ink painted; BCR returns full layout rect; textContent intact textContent; BCR non-zero check; display!=none; requires parsing inset() percentages and checking zero-area collapse Critical
SA-CSS-CLP-002: clip-path=inset(0 0 0 99%) — only 1% of width visible Left side inset 99%; only rightmost 1% of width exposed; permission terms hidden; BCR unchanged; partial-clip threshold <20% visible textContent; BCR non-zero; requires computing visible-area fraction from inset values; threshold: visibleWidth < 20% Critical
SA-CSS-CLP-003: per-element scope clip; header unclipped Header text no clip-path (visible); consent scope text clip-path=inset(100%) (zero area); any-visible-text check passes; per-element required textContent; any-visible-text check passes (header renders); per-element clip-path analysis of each text element required High
SA-CSS-CLP-004: mousedown injects clip-path=inset(100%); reverts mouseup No clip-path at rest; mousedown injects full clip (SVG attr or CSS style); text fully clipped during click; BCR preserved during injection; removed at mouseup Static DOM scan; requires MutationObserver on clip-path + style attrs with subtree:true + synthetic mousedown simulation Critical

SkillAudit findings for this attack surface

CRITICAL SA-CSS-CLP-001: clip-path=inset(100%) on text element — clipping rectangle collapses to zero area; no ink painted; getBoundingClientRect returns valid non-zero dimensions; textContent intact; display not none; element in accessibility tree; detection requires parsing the inset() function from getComputedStyle(el).clipPath and verifying the resulting clip region has non-zero area — BCR and textContent checks are insufficient.
CRITICAL SA-CSS-CLP-002: partial clip-path=inset(0 0 0 99%) — left side inset 99% exposes only rightmost 1% of element width; all permission terms hidden; right-side sliver exposes at most a partial last glyph; BCR unchanged; detection requires computing the effective visible fraction (100 - left% - right%) and flagging elements where visible width percentage is below threshold (20%); textContent intact and passes naive checks.
HIGH SA-CSS-CLP-003: per-element scope clip-path=inset(100%) on consent scope text with unclipped header — header element renders normally (no clip-path); consent scope text is fully clipped (zero visible area); scanner finding visible text in the SVG passes the panel; per-element clip-path analysis of each text and tspan element independently is required; sibling element visibility does not exempt the targeted element from inspection.
CRITICAL SA-CSS-CLP-004: JS mousedown injects clip-path=inset(100%) on consent text or parent group; text fully clipped during click window; bounding box preserved (BCR non-zero during injection); removed at mouseup; no clip-path at rest; MutationObserver with subtree:true required watching both the clip-path presentation attribute and the style attribute (CSS clipPath property), combined with synthetic mousedown simulation to trigger the handler.

Related: SVG mask consent attacks  |  SVG visibility consent attacks  |  SVG opacity consent attacks

← Blog  |  Security Checklist