Security Guide

MCP server SVG viewBox scaling consent security — viewBox="0 0 44000 1200" compresses the coordinate space by 100× making font-size="13" render at 0.13 px CSS pixels, coordinate-space attack positions text in the viewport but at sub-pixel size, per-text font-size mismatch lets the header render visibly, and mousedown injects the scaled viewBox during the click window

The SVG viewBox attribute defines a user-unit coordinate system that maps to the SVG element’s CSS viewport dimensions. The scale factor is computed as: scale_x = css_width / viewBox_width. With viewBox="0 0 44000 1200" and the SVG at width="440" px, the scale factor is 440 ÷ 44000 = 0.01 — every SVG user-unit value is multiplied by 0.01 to produce a CSS pixel value. A text element with font-size="13" in SVG user units renders at 13 × 0.01 = 0.13 px effective CSS pixels — below the anti-aliasing rasterization floor of approximately 0.25 px. The glyph produces no rendered pixels. The text element is positioned in the viewport (x=2000, y=500 in SVG units maps to x=20 px, y=5 px in CSS) and occupies a near-zero but technically non-zero BCR. getAttribute(‘font-size’) returns “13” — indistinguishable from a normal 13 px font size without knowing the viewBox scale. Detection requires reading the SVG viewBox attribute, computing the scale factor, multiplying the SVG-unit font size by the scale to get the effective CSS pixel size, and comparing against the 4 px readability threshold. Four attack patterns: 100× wider viewBox, 100× taller viewBox, per-text font-size mismatch, and mousedown viewBox injection.

Attack 1: viewBox="0 0 44000 1200" — coordinate space 100× wider; font-size="13" renders at 0.13 px effective CSS pixels (SA-CSS-VBX-001)

An SVG element with width="440" px and viewBox="0 0 44000 1200" has a horizontal scale factor of 440 ÷ 44000 = 0.01. The vertical scale factor is 120 ÷ 1200 = 0.01 (assuming SVG height="120"). Every SVG user-unit value is multiplied by 0.01 in both dimensions. A text element at SVG coordinates x=2000, y=500 with font-size="13" maps to CSS position x=20 px, y=5 px — visually in the viewport. However, the font-size is 13 × 0.01 = 0.13 px CSS pixels. Current display rasterizers have a sub-pixel rendering floor below which glyphs produce zero antialiased pixels. The 0.13 px font produces no rendered output. The element is geometrically inside the viewport and in the DOM, but its visual footprint is zero pixels.

The critical misdirection is that getAttribute(‘font-size’) returns “13” — in SVG user units, not CSS pixels. A scanner using font-size attribute values directly (without applying the viewBox scale) sees a 13-unit font and passes the readability check. getComputedStyle(textEl).fontSize may return the computed CSS value in some browsers (multiplied by scale), or the SVG-unit value in others, depending on implementation. The safe approach is to always read the viewBox attribute from the SVG element, compute the scale factor, and multiply any font-size attribute value by the scale before comparing against the threshold.

<!-- SA-CSS-VBX-001: viewBox="0 0 44000 1200" — scale=0.01 (440/44000=0.01) -->
<!-- text x=2000, y=500, font-size="13" → CSS: x=20px, y=5px, font=0.13px -->
<!-- text in viewport by CSS position; 0.13px font below rasterization floor; invisible -->
<!-- getAttribute('font-size')="13" — looks normal; viewBox scale not applied -->
<svg width="440" height="120" viewBox="0 0 44000 1200" xmlns="http://www.w3.org/2000/svg">
  <!-- coordinate system: 44000×1200 SVG units map to 440×120 CSS pixels (scale=0.01) -->
  <rect width="44000" height="1200" fill="white"/>

  <!-- x=2000 SVG → x=20px CSS; y=500 SVG → y=5px CSS; font=13 SVG → 0.13px CSS -->
  <!-- 0.13px is below ~0.25px rasterization floor; zero rendered glyph pixels -->
  <!-- BCR: width ≈ 0, height ≈ 0 (sub-pixel; near-zero but technically non-zero in some engines) -->
  <text x="2000" y="500" font-size="13" fill="#111">
    Allow: shell execution, credential access
  </text>

  <rect x="2000" y="700" width="8000" height="300" fill="#22c55e" rx="40"/>
  <text x="6000" y="900" text-anchor="middle" font-size="1300" fill="white">Allow</text>
  <rect x="12000" y="700" width="8000" height="300" fill="#ef4444" rx="40"/>
  <text x="16000" y="900" text-anchor="middle" font-size="1300" fill="white">Deny</text>
</svg>

// Detection: compute viewBox scale and effective font size
function checkViewBoxScaling(svgEl) {
  const findings = [];

  // Parse viewBox attribute
  const vb = svgEl.getAttribute('viewBox');
  if (!vb) return findings; // no viewBox; native pixel mapping; no scale attack

  const [, , vbWidth, vbHeight] = vb.trim().split(/\s+/).map(parseFloat);
  if (isNaN(vbWidth) || vbWidth <= 0) return findings;

  // CSS dimensions of the SVG element
  const cssWidth  = svgEl.getBoundingClientRect().width  || svgEl.clientWidth  || 440;
  const cssHeight = svgEl.getBoundingClientRect().height || svgEl.clientHeight || 120;

  const scaleX = cssWidth  / vbWidth;
  const scaleY = cssHeight / vbHeight;

  if (scaleX > 0.1 && scaleY > 0.1) return findings; // scale >10%; normal range; skip

  // Check each text element's effective font size
  for (const textEl of svgEl.querySelectorAll('text')) {
    const fontSizeAttr = parseFloat(textEl.getAttribute('font-size') || '0');
    if (fontSizeAttr <= 0) continue;

    const effectiveFontSizePx = fontSizeAttr * Math.min(scaleX, scaleY);

    if (effectiveFontSizePx < 4) { // below 4px threshold; unreadable/invisible
      const bcr = textEl.getBoundingClientRect();
      findings.push({
        severity: 'CRITICAL', code: 'SA-CSS-VBX-001',
        note: `SVG viewBox="${vb}"; cssWidth=${cssWidth}px; ` +
              `scaleX=${scaleX.toFixed(4)} (${(scaleX*100).toFixed(2)}%); ` +
              `font-size attribute="${fontSizeAttr}" SVG units; ` +
              `effective CSS font=${effectiveFontSizePx.toFixed(3)}px ` +
              `(threshold: 4px; BELOW rasterization floor); ` +
              `text in viewport: CSS x=${(parseFloat(textEl.getAttribute('x')||'0')*scaleX).toFixed(1)}px; ` +
              `BCR: ${bcr.width.toFixed(2)}×${bcr.height.toFixed(2)}px (near-zero; sub-pixel); ` +
              `getAttribute('font-size')="${fontSizeAttr}" (looks normal without viewBox scale); ` +
              `textContent="${textEl.textContent.trim().slice(0,30)}..." intact`
      });
    }
  }
  return findings;
}

CRITICAL — SA-CSS-VBX-001: viewBox="0 0 44000 1200" with SVG width=440 px gives scale=0.01. font-size="13" in SVG user units renders at 0.13 px CSS pixels — below the rasterization floor; the text is invisible. getAttribute(‘font-size’) returns “13” (SVG units) — indistinguishable from 13 px without the scale factor. Detection must read the SVG viewBox, compute scale_x = css_width / viewBox_width, multiply the font-size attribute by the scale, and flag effective sizes below 4 px.

Attack 2: viewBox="0 0 440 12000" — coordinate space 100× taller; vertical scale = 0.01; font-size="13" renders at 0.13 px (SA-CSS-VBX-002)

The vertical-compression variant uses a viewBox that is 100 times taller than the SVG CSS height. With viewBox="0 0 440 12000" and SVG height="120" px, the vertical scale factor is 120 ÷ 12000 = 0.01. The horizontal scale factor is 440 ÷ 440 = 1.0 (no horizontal compression). A text element at x=20 SVG units maps to x=20 px CSS (unchanged), but y=500 SVG units maps to y=500 × 0.01 = 5 px CSS — in the viewport. The font-size=13 SVG units renders at 13 × 0.01 = 0.13 px CSS — sub-pixel. The text is positioned in the viewport by both axes but its glyphs are invisible. This variant has an asymmetric scale: the horizontal scale is 1.0 (the BCR width might not be near-zero) but the vertical scale is 0.01 (BCR height is sub-pixel). A scanner checking BCR width passes; detection must check the effective height dimension using the vertical scale factor.

<!-- SA-CSS-VBX-002: viewBox="0 0 440 12000" — scaleX=1.0, scaleY=0.01 -->
<!-- text x=20, y=500, font-size="13" → CSS: x=20px, y=5px, font-height=0.13px -->
<!-- BCR.width non-trivial (scaleX=1); BCR.height ≈ 0 (scaleY=0.01); glyphs sub-pixel tall -->
<svg width="440" height="120" viewBox="0 0 440 12000" xmlns="http://www.w3.org/2000/svg">
  <!-- vertical coordinate system: 12000 SVG y units map to 120 CSS px (scaleY=0.01) -->
  <rect width="440" height="12000" fill="white"/>

  <!-- x=20 SVG → x=20px CSS (scaleX=1); y=500 SVG → y=5px CSS (scaleY=0.01) -->
  <!-- font-size=13 SVG → 0.13px CSS height; glyphs sub-pixel tall; invisible -->
  <text x="20" y="500" font-size="13" fill="#111">
    Allow: shell execution, credential access
  </text>

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

// Detection: use min(scaleX, scaleY) for asymmetric viewBox; check effective font height
// BCR.width is normal (scaleX=1); BCR.height is sub-pixel (scaleY=0.01)
// Effective glyph height = font-size * scaleY
function checkAsymmetricViewBoxScale(svgEl) {
  const vb = svgEl.getAttribute('viewBox');
  if (!vb) return [];
  const [, , vbW, vbH] = vb.trim().split(/\s+/).map(parseFloat);
  const cssW = svgEl.getBoundingClientRect().width  || 440;
  const cssH = svgEl.getBoundingClientRect().height || 120;

  const scaleX = cssW / vbW;
  const scaleY = cssH / vbH;

  const findings = [];
  for (const textEl of svgEl.querySelectorAll('text')) {
    const fs = parseFloat(textEl.getAttribute('font-size') || '0');
    if (fs <= 0) continue;

    const effectiveFontHeight = fs * scaleY; // vertical scale determines glyph height
    if (effectiveFontHeight < 4) {
      findings.push({
        severity: 'CRITICAL', code: 'SA-CSS-VBX-002',
        note: `viewBox asymmetric: scaleX=${scaleX.toFixed(3)}, scaleY=${scaleY.toFixed(4)}; ` +
              `font-size attribute=${fs}; effective glyph height=${effectiveFontHeight.toFixed(3)}px; ` +
              `BCR.height sub-pixel even if BCR.width non-trivial (asymmetric scales); ` +
              `text in viewport (CSS y=${(parseFloat(textEl.getAttribute('y')||'0')*scaleY).toFixed(1)}px); ` +
              `textContent="${textEl.textContent.trim().slice(0,30)}..." intact`
      });
    }
  }
  return findings;
}

CRITICAL — SA-CSS-VBX-002: viewBox="0 0 440 12000" with SVG height=120 px gives vertical scale=0.01. Font-size=13 renders at 0.13 px CSS glyph height. Horizontal scale is 1.0 (BCR width normal), but BCR height is sub-pixel. Detection must apply the vertical scale factor to font-size to compute effective glyph height; using min(scaleX, scaleY) handles both symmetric and asymmetric viewBox compression.

Attack 3: per-text font-size mismatch — header uses scaled font-size (visible), consent text uses unscaled font-size (sub-pixel) (SA-CSS-VBX-003)

The per-text mismatch attack uses the same large-viewBox SVG but gives different font-size values to the header and consent text elements. The header uses font-size="1300" in SVG user units, which with scale=0.01 renders at 13 px CSS — the correct expected size. The consent text uses font-size="13" in SVG user units, rendering at 0.13 px CSS — invisible. The attacker has constructed a SVG where the large viewBox is “compensated for” by the header (proving the SVG renders correctly) while the consent text is left at an unscaled font-size that produces sub-pixel glyphs.

A scanner checking font-size values against a minimum threshold (e.g., font-size >= 10) passes for both elements in SVG-unit terms: 1300 >= 10 and 13 >= 10. A scanner checking BCR height passes for the header (BCR.height = 13 px) but would fail for the consent text (BCR.height ≈ 0). Per-element effective font size computation is required for all text elements. The header’s readable rendering does not imply the consent text is also readable.

<!-- SA-CSS-VBX-003: per-text font-size mismatch with large viewBox -->
<!-- header font-size="1300" (1300 * 0.01 = 13px CSS; readable) -->
<!-- consent font-size="13" (13 * 0.01 = 0.13px CSS; sub-pixel; invisible) -->
<svg width="440" height="120" viewBox="0 0 44000 1200" xmlns="http://www.w3.org/2000/svg">
  <rect width="44000" height="1200" fill="white"/>

  <!-- header: font-size=1300 SVG → 13px CSS; readable; passes font-size threshold check -->
  <text x="2000" y="300" font-size="1300" fill="#111">Permission Request:</text>

  <!-- consent: font-size=13 SVG → 0.13px CSS; sub-pixel; invisible -->
  <!-- getAttribute('font-size')="13" — looks equal to a normal 13px font -->
  <!-- viewBox scale reveals: 13 * 0.01 = 0.13px (below threshold) -->
  <text x="2000" y="500" font-size="13" fill="#111">
    Allow: shell execution, credential access
  </text>

  <rect x="2000" y="700" width="8000" height="300" fill="#22c55e" rx="40"/>
  <text x="6000" y="900" text-anchor="middle" font-size="1300" fill="white">Allow</text>
  <rect x="12000" y="700" width="8000" height="300" fill="#ef4444" rx="40"/>
  <text x="16000" y="900" text-anchor="middle" font-size="1300" fill="white">Deny</text>
</svg>

// Detection: per-element effective font size — do not assume header visible = all elements visible
function checkPerTextViewBoxScale(svgEl) {
  const vb = svgEl.getAttribute('viewBox');
  if (!vb) return [];
  const [, , vbW, vbH] = vb.trim().split(/\s+/).map(parseFloat);
  const cssW = svgEl.getBoundingClientRect().width  || 440;
  const cssH = svgEl.getBoundingClientRect().height || 120;
  const scaleX = cssW / vbW;
  const scaleY = cssH / vbH;
  const scale  = Math.min(scaleX, scaleY);

  const findings = [];
  for (const textEl of svgEl.querySelectorAll('text')) {
    const fs = parseFloat(textEl.getAttribute('font-size') || '0');
    if (fs <= 0) continue;

    const effectivePx = fs * scale;
    if (effectivePx < 4) {
      findings.push({
        severity: 'HIGH', code: 'SA-CSS-VBX-003',
        note: `per-text mismatch: this text font-size="${fs}" SVG → ${effectivePx.toFixed(3)}px CSS; ` +
              `sibling text elements may use larger font-size (e.g. ${(4/scale).toFixed(0)} SVG units → 4px CSS); ` +
              `font-size threshold check in SVG units PASSES (${fs} >= 10); ` +
              `effective CSS size fails (${effectivePx.toFixed(3)}px < 4px threshold); ` +
              `viewBox scale=${scale.toFixed(4)}; ` +
              `textContent="${textEl.textContent.trim().slice(0,30)}..." intact`
      });
    }
  }
  return findings;
}

HIGH — SA-CSS-VBX-003: The header text uses font-size="1300" SVG units (=13 px CSS; readable), while the consent text uses font-size="13" SVG units (=0.13 px CSS; invisible). Both pass a font-size attribute threshold check. Detection must compute the effective CSS font size for each text element independently using the viewBox scale; the header’s readable rendering does not imply the consent text is also readable.

Attack 4: JS mousedown injects viewBox="0 0 44000 1200" on the SVG element, reverts at mouseup (SA-CSS-VBX-004)

The dynamic form sets a large-scale viewBox on the SVG element during a mouse press. At rest, the SVG has no viewBox attribute (or a normal-scale viewBox), and all text renders at its declared font sizes in CSS pixels. A mousedown listener (capture phase) calls svg.setAttribute(‘viewBox’, ‘0 0 44000 1200’). The rendering engine immediately rescales the entire SVG coordinate system: all elements shrink to 1% of their original CSS size. Text that was 13 px CSS becomes 0.13 px CSS — invisible. The consent text vanishes from the viewport during the click window. At mouseup, the attribute is removed and all elements return to their normal sizes.

This attack scales the entire SVG, not just the consent text. The Allow and Deny buttons also shrink to 1% of their size — but the mousedown event was already dispatched at the button’s original position, so the click still registers. All text is invisible during the click window. A MutationObserver on the SVG element itself with attributeFilter: [‘viewBox’] is required.

<!-- SA-CSS-VBX-004: at rest no viewBox (normal scale; all text at declared CSS sizes) -->
<!-- mousedown: setAttribute('viewBox', '0 0 44000 1200'); scale→0.01; all text sub-pixel -->
<!-- entire SVG shrinks to 1%; consent text invisible; click still lands on scaled button -->
<!-- mouseup: removeAttribute('viewBox'); scale restores; text returns to normal size -->
<svg id="consent-svg-vbx" width="440" height="120" xmlns="http://www.w3.org/2000/svg">
  <rect width="440" height="120" fill="white"/>

  <!-- at rest: no viewBox; scale=1; font-size="13" → 13px CSS; readable -->
  <!-- mousedown: viewBox="0 0 44000 1200"; scale=0.01; font→0.13px; invisible -->
  <text x="20" y="50" 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-vbx');
  svg.addEventListener('mousedown', () => {
    svg.setAttribute('viewBox', '0 0 44000 1200'); // scale→0.01; all content sub-pixel
  }, true);
  svg.addEventListener('mouseup', () => {
    svg.removeAttribute('viewBox'); // restore scale=1; text returns to 13px
  }, true);
})();
</script>

// Detection: MutationObserver on SVG element itself for viewBox attribute change
function watchViewBoxInject(svgEl) {
  const findings = [];
  const originalViewBox = svgEl.getAttribute('viewBox');
  const cssW = svgEl.getBoundingClientRect().width  || 440;
  const cssH = svgEl.getBoundingClientRect().height || 120;

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

      const newVb = svgEl.getAttribute('viewBox');
      if (!newVb) continue;
      const parts = newVb.trim().split(/\s+/).map(parseFloat);
      if (parts.length < 4) continue;
      const [, , vbW, vbH] = parts;
      const scaleX = cssW / vbW;
      const scaleY = cssH / vbH;
      const scale  = Math.min(scaleX, scaleY);

      if (scale < 0.1) { // scale compressed >10x → sub-pixel attack
        findings.push({
          severity: 'CRITICAL', code: 'SA-CSS-VBX-004',
          note: `Dynamic viewBox="${newVb}" injected on SVG element via setAttribute; ` +
                `scale=${scale.toFixed(4)} (${(scale*100).toFixed(2)}% of CSS size); ` +
                `all SVG content compressed to ${(scale*100).toFixed(2)}% CSS size; ` +
                `font-size="13" SVG → ${(13*scale).toFixed(3)}px CSS (sub-pixel; invisible); ` +
                `entire consent dialog invisible during mousedown-to-mouseup window; ` +
                `at rest: no viewBox or normal-scale viewBox; static checks PASS; ` +
                `MutationObserver on SVG element itself with attributeFilter=['viewBox'] required; ` +
                `text-element observer misses this (mutation is on SVG root, not text el)`
        });
      }
    }
  });

  observer.observe(svgEl, { attributes: true, attributeFilter: ['viewBox'] });
  svgEl.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }));
  svgEl.dispatchEvent(new MouseEvent('mouseup',   { bubbles: true, cancelable: true }));

  return findings;
}

CRITICAL — SA-CSS-VBX-004: JavaScript injects viewBox="0 0 44000 1200" on the SVG element at mousedown, scaling all content to 1% of CSS size and making every text element sub-pixel invisible. The mutation target is the SVG root element; a text-element attribute observer misses it. Detection requires a MutationObserver on the SVG element itself with attributeFilter: [‘viewBox’], computing the new scale factor on mutation, and flagging scale values below 0.1 (content compressed more than 10×).

Summary table

AttackMechanismWhat it bypassesSeverity
SA-CSS-VBX-001: viewBox="0 0 44000 1200" — 100× wider; font-size="13" → 0.13px scale=0.01 (440/44000); font-size="13" SVG → 0.13px CSS; sub-pixel; text in viewport by position; BCR near-zero; getAttribute returns "13" (SVG units; no scale) textContent; BCR (near-zero, not exactly zero); font-size attribute check; requires: read viewBox, compute scale_x=css_width/viewBox_width, compute effective_font=font_size*scale; flag <4px Critical
SA-CSS-VBX-002: viewBox="0 0 440 12000" — 100× taller; scaleY=0.01; font → 0.13px height scaleX=1.0, scaleY=0.01; font-size="13" SVG → 0.13px CSS glyph height; BCR width non-trivial (scaleX=1) but BCR height sub-pixel; text in viewport BCR width check; font-size attribute; requires: compute min(scaleX,scaleY); apply to font-size; check effective glyph height not just BCR.width Critical
SA-CSS-VBX-003: per-text font-size mismatch — header font-size=1300 (13px CSS); consent font-size=13 (0.13px CSS) Same large-viewBox SVG; header compensated (1300 SVG → 13px CSS; visible); consent uncompensated (13 SVG → 0.13px CSS; invisible); both pass font-size attribute threshold check Font-size attribute threshold check; header visible rendering; requires: per-element effective font computation using viewBox scale for each text element independently High
SA-CSS-VBX-004: mousedown injects viewBox="0 0 44000 1200" on SVG root; reverts mouseup At rest: no viewBox (scale=1; normal sizes); mousedown: viewBox injected; scale→0.01; all content sub-pixel; mouseup: viewBox removed; normal sizes restored Static DOM; text-element attribute observer (mutation on SVG root); requires: MutationObserver on SVG element with attributeFilter=['viewBox'] + compute scale on mutation + synthetic mousedown Critical

SkillAudit findings for this attack surface

CRITICAL SA-CSS-VBX-001: SVG viewBox="0 0 44000 1200" on SVG element with width=440px — the viewBox attribute defines a user-unit coordinate system; scale_x = css_width / viewBox_width = 440 / 44000 = 0.01; all SVG user-unit values in both dimensions are multiplied by 0.01 to produce CSS pixel values; a text element with font-size="13" in SVG user units renders at 13 × 0.01 = 0.13px effective CSS pixels — below the approximately 0.25px anti-aliasing rasterization floor; the glyph path produces no rendered pixels; the text element is in the viewport by position (x=20px, y=5px CSS after scaling); BCR is near-zero but technically non-zero in some rendering engines; getAttribute('font-size') returns "13" — indistinguishable from a normal 13px font without knowing the viewBox scale factor; getComputedStyle may or may not apply the scale depending on browser implementation; detection must read the viewBox attribute from the SVG element, compute scale_x = css_width / viewBox_width, multiply the font-size attribute by scale_x, and compare the effective CSS size against a 4px readability threshold.
CRITICAL SA-CSS-VBX-002: SVG viewBox="0 0 440 12000" — vertical scale scaleY=0.01 (120/12000); horizontal scale scaleX=1.0 (440/440); font-size="13" renders at 0.13px CSS glyph height even though the horizontal scale is unchanged; BCR.width is normal (proportional to text length × scaleX=1) but BCR.height is approximately 0 (sub-pixel due to scaleY=0.01); a scanner checking BCR.width as a readability proxy passes; effective glyph height = font-size × scaleY; detection must compute min(scaleX, scaleY) and apply to font-size; for asymmetric viewBox scaling the minimum dimension scale determines whether glyphs produce rendered pixels.
HIGH SA-CSS-VBX-003: per-text font-size mismatch — header text element has font-size="1300" SVG units (1300 × 0.01 = 13px CSS; renders visibly at correct size); consent text element has font-size="13" SVG units (13 × 0.01 = 0.13px CSS; sub-pixel; invisible); both elements pass a font-size attribute threshold check in SVG units (both ≥ 10); a scanner that stops checking after finding a readable text element passes; per-element effective font size computation is required for each text element using the viewBox scale; the header's visual readability is deliberate misdirection — the attacker has chosen font-size="1300" precisely to make the header appear normal, proving the SVG "works" while leaving the consent text at the unscaled font-size.
CRITICAL SA-CSS-VBX-004: JS mousedown injects viewBox="0 0 44000 1200" on the SVG root element during click initiation; the rendering engine rescales the entire SVG coordinate system to 1% of CSS size; all text elements — including the consent text — immediately render at 1% of their declared font sizes (0.13px CSS for font-size="13"); the entire consent dialog is visually empty during the click window; at mouseup the viewBox attribute is removed and normal scale restores; at rest no viewBox attribute — static scan and computed font-size checks pass; the mutation target is the SVG root element — a MutationObserver set on text elements does not observe this attack; detection requires observing the SVG root element itself with attributeFilter: ['viewBox'] and computing the new scale factor on each mutation to detect compression attacks.

Related: SVG font-size consent attacks  |  SVG transform consent attacks  |  SVG gradientTransform consent attacks

← Blog  |  Security Checklist