Security Guide

MCP server SVG transform consent security — transform="scale(0.001)" reduces consent text to 0.001× its normal rendered size, translate(5000, 0) moves text entirely off-screen, per-element scope keeps the header visible while consent is transformed away, and mousedown injects a microscopic scale during the click window

The SVG transform presentation attribute applies a geometric coordinate transformation to an element’s local coordinate system, affecting all rendering output including glyph sizes, glyph positions, and the element’s bounding box as reported by getBoundingClientRect(). Two distinct transform primitives enable consent text suppression: scale(f) multiplies all rendered dimensions by f, and translate(tx, ty) repositions the element’s origin by the given offsets. With scale(0.001), a 13px font becomes 0.013px in viewport pixels — 1000 times smaller than the legibility threshold — and the BCR dimensions contract proportionally to near-zero. With translate(5000, 0), the text is displaced 5000 SVG units to the right of its declared position; with a 440px SVG viewport and overflow="hidden" (the default), the text is entirely clipped. Both transforms leave textContent intact and the element in the DOM. Per-element scope applies a suppressive transform to the consent text element alone, leaving a header element untouched and visible to demonstrate that the SVG renders normally. Four attack patterns: scale-to-microscopic, translate-off-screen, per-element scope, and mousedown injection.

Attack 1: transform="scale(0.001)" on SVG <text> — 0.001× rendered size, near-zero BCR (SA-CSS-TRF-001)

SVG’s transform attribute establishes a new local coordinate system for the element by applying the specified matrix transformation. scale(0.001) is equivalent to the matrix matrix(0.001, 0, 0, 0.001, 0, 0): every coordinate in the element’s local space is multiplied by 0.001 before being mapped to the parent coordinate space. For a text element, this means the font metrics (em-box, advance widths, kerning) are all scaled by 0.001 when computing the rendered output. A font-size="13" text element under scale(0.001) renders at an effective size of 13 × 0.001 = 0.013px — three orders of magnitude below the 4px legibility floor and far below the rasterization minimum of any rendering engine.

The critical difference from a sub-pixel font-size attack is how BCR responds. When font-size="0.1" is set directly, the browser’s layout engine uses the 0.1px font-size to compute the text block’s geometry, but the resulting BCR width (the sum of advance widths at 0.1px) is still measurably non-zero for long text strings. Under scale(0.001), the BCR dimensions are the full-size layout box scaled by 0.001: a 200px-wide text block at normal size becomes 0.2px wide in the BCR. A scanner using BCR.height > 2px as a visibility proxy correctly flags the scaled text, whereas the same check on a directly-set 0.1px font-size might still show a BCR height of 1-2px due to layout conservatism. Detection approach: extract scale factors from the transform attribute by parsing the scale() function or decomposing the matrix() function; compute the effective rendered font-size as font_size_svg × scale_y; flag as CRITICAL if the result is below 4px. Also flag independently if BCR.width > 0 && BCR.height < 2px for any consent text element (a near-zero BCR height on text with non-empty content is a strong transform-attack signal).

<!-- SA-CSS-TRF-001: transform="scale(0.001)" on SVG <text> — 0.001x rendered size -->
<!-- 13px font × 0.001 = 0.013px effective; below rasterization floor; zero glyph pixels -->
<!-- BCR.width ≈ 0.2px; BCR.height ≈ 0.013px (near-zero, detectable) -->
<svg width="440" height="120" xmlns="http://www.w3.org/2000/svg">
  <rect width="440" height="120" fill="white"/>

  <!-- transform=scale(0.001): entire local coordinate system scaled 0.001x -->
  <!-- font-size="13" → effective 0.013px; glyph outlines 0.013px tall; invisible -->
  <!-- BCR reflects post-transform viewport pixels: near-zero dimensions -->
  <!-- textContent intact; getComputedStyle(el).fontSize = "13px" (untransformed CSS value) -->
  <text x="20" y="40" font-size="13" fill="#111"
        transform="scale(0.001)">
    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: parse transform attribute for scale factors; compute effective rendered font-size
function checkScaleTransform(svgEl) {
  const findings = [];
  const MIN_LEGIBLE_PX = 4;
  const MIN_BCR_HEIGHT  = 2; // px; near-zero BCR height signals transform attack

  for (const el of svgEl.querySelectorAll('text, tspan')) {
    const transformAttr = el.getAttribute('transform') || '';
    if (!transformAttr) continue;

    let scaleX = 1, scaleY = 1;

    // Parse scale(sx) or scale(sx, sy)
    const scaleMatch = transformAttr.match(/scale\(\s*([\d.eE+-]+)(?:\s*,\s*([\d.eE+-]+))?\s*\)/);
    if (scaleMatch) {
      scaleX = parseFloat(scaleMatch[1]);
      scaleY = scaleMatch[2] !== undefined ? parseFloat(scaleMatch[2]) : scaleX;
    }

    // Parse matrix(a,b,c,d,e,f) — scale factors are a (scaleX) and d (scaleY)
    const matrixMatch = transformAttr.match(
      /matrix\(\s*([\d.eE+-]+)\s*,\s*([\d.eE+-]+)\s*,\s*([\d.eE+-]+)\s*,\s*([\d.eE+-]+)/
    );
    if (matrixMatch) {
      scaleX = parseFloat(matrixMatch[1]);
      scaleY = parseFloat(matrixMatch[4]);
    }

    const svgFontSize = parseFloat(el.getAttribute('font-size') || getComputedStyle(el).fontSize);
    const effectiveSize = svgFontSize * Math.abs(scaleY);

    // BCR check as secondary signal
    const bcr = el.getBoundingClientRect();

    const scaleIsMicroscopic = Math.abs(scaleX) < 0.05 || Math.abs(scaleY) < 0.05;
    const bcrIsTiny = bcr.width > 0 && bcr.height < MIN_BCR_HEIGHT;

    if (scaleIsMicroscopic || (effectiveSize > 0 && effectiveSize < MIN_LEGIBLE_PX)) {
      findings.push({
        severity: 'CRITICAL', code: 'SA-CSS-TRF-001',
        note: `<${el.tagName}> transform="${transformAttr}"; ` +
              `scale(${scaleX.toExponential(3)}, ${scaleY.toExponential(3)}); ` +
              `SVG font-size=${svgFontSize}px × scaleY=${scaleY} = effective ${effectiveSize.toExponential(3)}px; ` +
              `below ${MIN_LEGIBLE_PX}px legibility threshold; zero glyph pixels; ` +
              `BCR: w=${bcr.width.toFixed(4)} h=${bcr.height.toFixed(4)} (near-zero — BCR check also flags); ` +
              `getComputedStyle(el).fontSize="${getComputedStyle(el).fontSize}" (CSS value; transform not included); ` +
              `textContent="${el.textContent.trim().slice(0,30)}..." intact`
      });
    }
  }
  return findings;
}

CRITICAL — SA-CSS-TRF-001: transform="scale(0.001)" reduces all rendered dimensions by a factor of 1000. A font-size="13" text element renders at an effective 0.013px — invisible. Unlike font-size attacks, BCR also shrinks: BCR.height < 2px on a non-empty text element signals this attack. Detection must parse the transform attribute for scale factors and multiply by the element’s font-size to compute the effective rendered size; also check BCR.height < 2px on text with non-empty textContent as a secondary signal.

Attack 2: transform="translate(5000, 0)" — text displaced entirely off-screen, SVG overflow clips it (SA-CSS-TRF-002)

The translate form of the transform attack exploits SVG’s default overflow behavior. According to the SVG specification, the default value of overflow on an <svg> element is hidden: content that extends beyond the SVG viewport is clipped. The text element is declared with x="20" y="40", positioning it within the visible area. But transform="translate(5000, 0)" shifts the element’s local origin 5000 SVG user units to the right before the x="20" attribute is applied. The effective x-position is 5000 + 20 = 5020. With a viewport width of 440px (and one user unit equaling one CSS pixel at the default transform), the text is positioned 5020px off the right edge of the SVG viewport and is entirely clipped by the overflow:hidden default.

The getBoundingClientRect() of the translated element reflects the translated position in viewport coordinates. The returned BCR.x is approximately equal to svgRect.x + 5020, placing it far to the right of both the SVG container and the browser window. This provides a reliable detection signal: compute the intersection of the text element’s BCR with the SVG container’s BCR; if there is zero overlap (the text BCR lies entirely outside the container BCR), the text is off-screen and clipped. A secondary signal: BCR.x > window.innerWidth or BCR.right < 0 indicate the element is not in the visible viewport. textContent is intact throughout; the DOM element is not hidden or removed. A scanner checking only DOM membership and non-empty textContent sees a valid element and passes — but the text is geometrically outside the visible region.

<!-- SA-CSS-TRF-002: transform="translate(5000, 0)" on SVG <text> — displaced off-screen -->
<!-- Declared x=20 y=40; effective x = 5000+20 = 5020 (440px SVG width); entirely clipped -->
<!-- SVG overflow=hidden (default): content beyond viewport is not rendered -->
<!-- BCR.x ≈ svgRect.x + 5020 (far off-screen); textContent intact; DOM membership check PASSES -->
<svg width="440" height="120" xmlns="http://www.w3.org/2000/svg">
  <rect width="440" height="120" fill="white"/>

  <!-- transform=translate(5000,0): effective x=5020; 440px SVG; text is 4580px off right edge -->
  <!-- overflow=hidden (SVG default): entirely clipped; zero pixels rendered in SVG viewport -->
  <!-- BCR.x far right of window; BCR intersection with SVG BCR is empty set -->
  <text x="20" y="40" font-size="13" fill="#111"
        transform="translate(5000, 0)">
    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 BCR intersection of text element with SVG container BCR
function checkTranslateOffScreen(svgEl) {
  const findings = [];
  const svgBcr = svgEl.getBoundingClientRect();

  for (const el of svgEl.querySelectorAll('text, tspan')) {
    const transformAttr = el.getAttribute('transform') || '';
    if (!transformAttr) continue;

    // Quick check: does the transform contain a translate?
    const hasTranslate = /translate\s*\(/.test(transformAttr) ||
                         /matrix\s*\(/.test(transformAttr);
    if (!hasTranslate) continue;

    const elBcr = el.getBoundingClientRect();

    // Compute intersection of element BCR with SVG container BCR
    const overlapX = Math.max(0,
      Math.min(elBcr.right,  svgBcr.right)  - Math.max(elBcr.left, svgBcr.left));
    const overlapY = Math.max(0,
      Math.min(elBcr.bottom, svgBcr.bottom) - Math.max(elBcr.top,  svgBcr.top));
    const intersectionArea = overlapX * overlapY;

    if (intersectionArea === 0 && el.textContent.trim().length > 0) {
      findings.push({
        severity: 'CRITICAL', code: 'SA-CSS-TRF-002',
        note: `<${el.tagName}> transform="${transformAttr}"; ` +
              `element BCR: x=${elBcr.x.toFixed(0)} y=${elBcr.y.toFixed(0)} ` +
              `w=${elBcr.width.toFixed(0)} h=${elBcr.height.toFixed(0)}; ` +
              `SVG container BCR: x=${svgBcr.x.toFixed(0)} y=${svgBcr.y.toFixed(0)} ` +
              `w=${svgBcr.width.toFixed(0)} h=${svgBcr.height.toFixed(0)}; ` +
              `intersection area=0 (no overlap): text entirely outside SVG viewport; ` +
              `SVG overflow=hidden (default): clipped to viewport; zero pixels rendered; ` +
              `textContent="${el.textContent.trim().slice(0,30)}..." intact; DOM check PASSES; ` +
              `BCR.x=${elBcr.x.toFixed(0)} >> window.innerWidth=${window.innerWidth}`
      });
    }
  }
  return findings;
}

CRITICAL — SA-CSS-TRF-002: transform="translate(5000, 0)" displaces the consent text element 5000 SVG units to the right of its declared position. The effective x-coordinate is 5020 in a 440px SVG; with overflow:hidden (SVG default), the element is entirely clipped. textContent is intact; the element is in the DOM; BCR is non-zero but positioned far outside both the SVG container and the window. Detection requires computing the intersection of the text element’s BCR with the SVG container’s BCR; zero intersection area on a non-empty text element signals this attack.

Attack 3: per-element transform="scale(0.001)" on consent text, header element untransformed (SA-CSS-TRF-003)

The per-element scope attack applies transform="scale(0.001)" directly to the consent text element while the header text element has no transform and renders at its normal size. Both elements are top-level <text> children of the SVG; neither is nested inside the other or inside a transformed <g> group. The header element is fully visible, demonstrating that the SVG is rendering text correctly. The consent element’s local coordinate system is scaled to 0.001x, making its 13px font render at 0.013px — invisible. A user opening the dialog sees a header label but no consent scope text beneath it.

This attack exploits the assumption that visible text in one element implies visible text in sibling elements. A scanner that finds the header element passes a legibility check and treats the consent text as similarly visible without independently verifying it. Per-element transform analysis is required: each element that carries consent-relevant content must have its own transform chain evaluated. The effective transform on an element is the concatenation of all transforms up the SVG tree: an element’s own transform attribute, multiplied by the transforms of its ancestor <g> groups, multiplied by any transforms on the root SVG element. A correct detection system must walk the ancestor chain, accumulating the transform matrix at each level, and apply the final effective matrix to the element’s font-size to compute the true rendered size. In this attack, the consent element’s own transform attribute is the only transform in the chain — but in more complex SVG structures, a transform on a grandparent <g> could suppress an entire subtree while appearing innocuous when the element itself is inspected in isolation.

<!-- SA-CSS-TRF-003: per-element scope — header <text> no transform; consent <text> scale(0.001) -->
<!-- Header element: font-size="13", no transform — fully visible (demonstrates SVG renders text) -->
<!-- Consent element: font-size="13", transform=scale(0.001) — 0.013px effective; invisible -->
<!-- Any-visible-text check passes (header is visible); per-element independence required -->
<svg width="440" height="140" xmlns="http://www.w3.org/2000/svg">
  <rect width="440" height="140" fill="white"/>

  <!-- header: no transform; font-size="13"; renders normally at 13px; visible -->
  <text x="20" y="30" font-size="13" fill="#111">Permission Request:</text>

  <!-- consent: transform=scale(0.001); font-size="13" × 0.001 = 0.013px; invisible -->
  <!-- header visibility does NOT imply consent text visibility; per-element check required -->
  <text x="20" y="52" font-size="13" fill="#111"
        transform="scale(0.001)">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 transform evaluation; accumulate transforms up the ancestor chain
function checkPerElementTransform(svgEl) {
  const findings = [];
  const MIN_LEGIBLE_PX = 4;
  const MIN_BCR_HEIGHT  = 2;

  function getEffectiveScale(el) {
    // Walk up the SVG tree accumulating scale factors
    let scaleX = 1, scaleY = 1;
    let node = el;
    while (node && node !== svgEl.parentElement) {
      if (node.nodeType === Node.ELEMENT_NODE) {
        const tf = node.getAttribute('transform') || '';
        const sm = tf.match(/scale\(\s*([\d.eE+-]+)(?:\s*,\s*([\d.eE+-]+))?\s*\)/);
        const mm = tf.match(
          /matrix\(\s*([\d.eE+-]+)\s*,\s*[\d.eE+-]+\s*,\s*[\d.eE+-]+\s*,\s*([\d.eE+-]+)/
        );
        if (sm) {
          scaleX *= parseFloat(sm[1]);
          scaleY *= sm[2] !== undefined ? parseFloat(sm[2]) : parseFloat(sm[1]);
        } else if (mm) {
          scaleX *= parseFloat(mm[1]);
          scaleY *= parseFloat(mm[2]);
        }
      }
      node = node.parentElement;
    }
    return { scaleX, scaleY };
  }

  for (const el of svgEl.querySelectorAll('text, tspan')) {
    const { scaleX, scaleY } = getEffectiveScale(el);
    if (Math.abs(scaleX) >= 0.05 && Math.abs(scaleY) >= 0.05) continue; // not microscopic

    const svgFs = parseFloat(el.getAttribute('font-size') || getComputedStyle(el).fontSize);
    const effectiveSize = svgFs * Math.abs(scaleY);
    const bcr = el.getBoundingClientRect();

    if (effectiveSize < MIN_LEGIBLE_PX || bcr.height < MIN_BCR_HEIGHT) {
      findings.push({
        severity: 'HIGH', code: 'SA-CSS-TRF-003',
        note: `<${el.tagName}> effective scale(${scaleX.toExponential(3)}, ${scaleY.toExponential(3)}) ` +
              `(accumulated from element + ancestor transforms); ` +
              `SVG font-size=${svgFs}px × scaleY → effective ${effectiveSize.toExponential(3)}px; ` +
              `BCR: w=${bcr.width.toFixed(4)} h=${bcr.height.toFixed(4)}; ` +
              `sibling header element untransformed and visible — any-visible-text check PASSES; ` +
              `per-element independent transform evaluation required; ` +
              `textContent="${el.textContent.trim().slice(0,30)}..."`
      });
    }
  }
  return findings;
}

HIGH — SA-CSS-TRF-003: Per-element transform="scale(0.001)" on the consent text element; the header text element has no transform and renders visibly at 13px. A scanner finding any visible text in the SVG cannot conclude all text is visible. Each consent-bearing element must be independently evaluated by accumulating its full transform chain (own attribute plus ancestor <g> transforms) and computing the effective rendered font size from the resulting scale factors.

Attack 4: JS mousedown injects transform="scale(0.001)", reverts at mouseup (SA-CSS-TRF-004)

The dynamic form of the transform attack applies transform="scale(0.001)" to the consent text element precisely during a mouse press. At rest, the consent text element has no transform attribute and renders normally at 13px — fully legible. A mousedown event listener on the SVG container (registered in the capture phase so it executes before any other listener) sets the element’s transform attribute to "scale(0.001)". The rendering engine immediately repaints with the scaled transform: the glyph pixels shrink to 0.013px — invisible. The BCR simultaneously contracts to near-zero width and height. At mouseup, the transform attribute is removed and the element reverts to its normal rendering.

The transform injection can also be delivered via the CSS transform property: el.style.transform = 'scale(0.001)'. In SVG, CSS transform and the transform presentation attribute interact: the CSS property takes precedence over the presentation attribute in the rendering cascade. A MutationObserver watching only the SVG transform presentation attribute will miss the CSS style property form; conversely, a scanner watching only the CSS style attribute will miss the presentation attribute form. Both channels must be monitored. After detecting a mutation that sets a microscopic scale, the scanner should also synthesize a BCR comparison: read getBoundingClientRect().height of the consent element immediately after the mutation and compare it to the pre-mutation BCR (or a fresh measurement taken at audit initialization). A height drop from ~15px to <0.02px is an unambiguous signal of scale-transform injection during interaction.

<!-- SA-CSS-TRF-004: at rest no transform (13px, visible); mousedown injects scale(0.001) -->
<!-- Effective 0.013px during click window; BCR near-zero; reverts to normal at mouseup -->
<!-- Static scan finds no transform attribute (at rest); all static checks PASS -->
<svg id="consent-svg-trf" width="440" height="120" xmlns="http://www.w3.org/2000/svg">
  <rect width="440" height="120" fill="white"/>

  <!-- at rest: no transform; font-size="13"; fully legible; static scan PASSES -->
  <!-- mousedown: transform set to scale(0.001); 0.013px effective; invisible during click -->
  <text id="scope-text-trf" 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-trf');
  const text = document.getElementById('scope-text-trf');
  svg.addEventListener('mousedown', () => {
    text.setAttribute('transform', 'scale(0.001)');
    // alternative CSS form (bypasses presentation-attribute observer):
    // text.style.transform = 'scale(0.001)';
  }, true); // capture phase
  svg.addEventListener('mouseup', () => {
    text.removeAttribute('transform');
    // alternative: text.style.transform = '';
  }, true);
})();
</script>

// Detection: MutationObserver on transform and style attrs; BCR verification after mutation
function watchTransformInject(svgEl) {
  const findings = [];
  const MIN_BCR_HEIGHT  = 2;   // px
  const MIN_SCALE_LEGIBLE = 0.05; // below 5% of original size = microscopic

  // Capture baseline BCR heights before any mutation
  const baselineHeights = new Map();
  for (const el of svgEl.querySelectorAll('text, tspan')) {
    baselineHeights.set(el, el.getBoundingClientRect().height);
  }

  const observer = new MutationObserver(mutations => {
    for (const mut of mutations) {
      const attr = mut.attributeName;
      if (attr !== 'transform' && attr !== 'style') continue;
      const el = mut.target;
      if (el.tagName !== 'text' && el.tagName !== 'tspan') continue;

      let transformVal = null;
      if (attr === 'transform') {
        transformVal = el.getAttribute('transform');
      } else {
        // Parse CSS style attribute for transform property
        const styleStr = el.getAttribute('style') || '';
        const match = styleStr.match(/\btransform\s*:\s*([^;]+)/i);
        transformVal = match ? match[1].trim() : null;
      }
      if (!transformVal) continue;

      // Extract scale from the transform value
      let scaleY = 1;
      const sm = transformVal.match(/scale\(\s*([\d.eE+-]+)(?:\s*,\s*([\d.eE+-]+))?\s*\)/);
      const mm = transformVal.match(
        /matrix\(\s*[\d.eE+-]+\s*,\s*[\d.eE+-]+\s*,\s*[\d.eE+-]+\s*,\s*([\d.eE+-]+)/
      );
      if (sm) scaleY = sm[2] !== undefined ? parseFloat(sm[2]) : parseFloat(sm[1]);
      else if (mm) scaleY = parseFloat(mm[1]);

      const bcrAfter = el.getBoundingClientRect();
      const bcrBefore = baselineHeights.get(el) ?? 0;

      if (Math.abs(scaleY) < MIN_SCALE_LEGIBLE || bcrAfter.height < MIN_BCR_HEIGHT) {
        findings.push({
          severity: 'CRITICAL', code: 'SA-CSS-TRF-004',
          note: `Dynamic transform="${transformVal}" injected via "${attr}" attribute ` +
                `on <${el.tagName}> during interaction; ` +
                `scaleY=${scaleY.toExponential(3)} (below ${MIN_SCALE_LEGIBLE} threshold); ` +
                `BCR height before=${bcrBefore.toFixed(2)}px → after=${bcrAfter.height.toFixed(4)}px; ` +
                `at rest: no transform; font-size=13px visible; static checks PASS; ` +
                `injected at mousedown; removed at mouseup; ` +
                `MutationObserver on transform + style attrs + synthetic mousedown + BCR comparison; ` +
                `textContent="${el.textContent?.trim().slice(0,30) ?? ''}..."`
        });
      }
    }
  });

  observer.observe(svgEl, {
    subtree: true, attributes: true,
    attributeFilter: ['transform', 'style']
  });

  // Synthetic interaction to trigger the attack
  svgEl.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }));
  svgEl.dispatchEvent(new MouseEvent('mouseup',   { bubbles: true, cancelable: true }));

  return findings;
}

CRITICAL — SA-CSS-TRF-004: JavaScript injects transform="scale(0.001)" at mousedown on the consent text element, shrinking it to 0.013px effective size during the click window. At rest, no transform attribute is set; the element renders at a legible 13px; all static checks pass. Detection requires a MutationObserver on both the transform presentation attribute and the style attribute (CSS transform property), combined with a synthetic mousedown dispatch and a post-mutation BCR height comparison to confirm the rendered size collapse.

Summary table

AttackMechanismWhat it bypassesSeverity
SA-CSS-TRF-001: transform="scale(0.001)" on <text> — 0.001x rendered size, near-zero BCR scale(0.001) scales element coordinate system; 13px font → 0.013px effective; BCR.width and BCR.height also scale to near-zero; textContent intact; getComputedStyle.fontSize = "13px" (CSS value, transform not applied) textContent; getComputedStyle.fontSize (returns pre-transform CSS value); requires: parse transform attribute scale factors, multiply by font-size, threshold effective rendered size; also: BCR.height < 2px on non-empty text Critical
SA-CSS-TRF-002: transform="translate(5000,0)" — text displaced off-screen, clipped translate(5000,0) shifts effective x to 5020px in 440px SVG; overflow:hidden (SVG default) clips content beyond viewport; BCR reflects translated off-screen position; textContent intact; DOM membership check passes textContent; DOM membership; BCR non-zero; requires: compute intersection of element BCR with SVG container BCR; zero intersection area on non-empty text signals off-screen displacement Critical
SA-CSS-TRF-003: per-element scale(0.001) on consent; header untransformed (visible) Header <text> no transform; consent <text> scale(0.001); header visible at 13px; consent 0.013px effective; any-visible-text check finds header and passes textContent; any-visible-text check passes; requires: per-element independent transform evaluation; accumulate full transform chain for each consent element High
SA-CSS-TRF-004: mousedown injects scale(0.001); reverts mouseup At rest: no transform; 13px visible; static checks pass. mousedown: scale(0.001); 0.013px; near-zero BCR. mouseup: transform removed; revert to normal Static DOM; requires: MutationObserver on transform + style attrs + synthetic mousedown + BCR before/after comparison (height collapse from ~15px to <0.02px) Critical

SkillAudit findings for this attack surface

CRITICAL SA-CSS-TRF-001: transform="scale(0.001)" on SVG <text> consent element — the scale(0.001) transform multiplies all rendered dimensions in the element’s local coordinate system by 0.001; a font-size="13" text element produces a 0.013px effective rendered size; well below any rendering engine’s rasterization floor; zero glyph pixels; BCR.width and BCR.height also scale to near-zero (BCR reflects post-transform viewport-pixel dimensions); getComputedStyle(el).fontSize returns the pre-transform CSS value "13px" (CSS font-size is not affected by SVG transform attribute); detection must parse the transform attribute for scale() or matrix() functions, extract the Y-axis scale factor, multiply by the SVG-attribute font-size, and threshold the product at 4px; BCR.height < 2px on non-empty consent text is a secondary ground-truth signal.
CRITICAL SA-CSS-TRF-002: transform="translate(5000, 0)" on SVG <text> consent element — translate(5000,0) adds 5000 SVG user units to the element’s x-axis position; declared x="20" → effective x=5020 in a 440px-wide SVG; default SVG overflow:hidden clips all content beyond the viewport boundary; consent text is not rendered within the SVG viewport; BCR.x reflects the translated off-screen position far beyond window.innerWidth; textContent intact; element is in the DOM and not hidden; DOM-membership and textContent checks both pass; detection must compute the geometric intersection of the text element’s BCR with the SVG container’s BCR; zero intersection area on a text element with non-empty content unambiguously signals off-screen displacement via translate transform.
HIGH SA-CSS-TRF-003: per-element transform="scale(0.001)" on consent <text> element; sibling header <text> element has no transform and renders visibly at 13px — any-visible-text scan finds the header element, passes a legibility check, and may conclude all consent text is visible; consent element independently has scale(0.001) making its effective rendered size 0.013px; each consent-bearing text element must be independently evaluated; effective transform is the product of the element’s own transform attribute and all ancestor <g> group transforms up the SVG tree; accumulating the full transform chain and computing effective font-size × accumulated-scaleY is required for correct detection.
CRITICAL SA-CSS-TRF-004: JS mousedown injects transform="scale(0.001)" on consent text element during click initiation; effective rendered size 0.013px; BCR height collapses to near-zero; text is invisible during the click window; transform removed at mouseup; at rest no transform attribute exists — static scan finds normal 13px font-size text and passes; requires MutationObserver with attributeFilter ['transform', 'style'] observing consent text elements; style attribute mutation requires parsing the inline style string for the CSS transform property value; synthetic mousedown dispatch triggers the attack; post-mutation BCR.height measurement (threshold < 2px) provides a rendering-based ground-truth confirmation independent of attribute parsing.

Related: SVG font-size consent attacks  |  SVG clip-path consent attacks  |  SVG viewBox consent attacks

← Blog  |  Security Checklist