← Blog

MCP server SVG text presentation attribute consent security

SVG <text> elements support a parallel attribute system for visual properties that is distinct from the CSS property system. fill, opacity, transform, and textLength as SVG presentation attributes have different APIs, different resolution rules, and different detection methods compared to their CSS equivalents. Audit tools built for HTML consent text — which query getComputedStyle for color, opacity, and transform — check the CSS property system and miss attacks delivered via SVG presentation attributes. Four SA-SVG-TXT patterns cover the primary SVG-specific consent attack vectors.

By SkillAudit  |  30 Oct 2026

SVG presentation attributes and CSS properties have a defined interaction in the SVG specification: CSS properties take precedence over presentation attributes for inherited properties (like fill and opacity), but the SVG attribute is the fallback when no CSS property is set. An audit tool that only inspects CSS computed properties will see the default CSS values, not the SVG presentation attribute values, creating a detection gap for SVG-specific attacks.

SA-SVG-TXT-001: SVG text fill="none" — no glyph fill pixels

SVG <text fill="none"> sets the SVG fill presentation attribute to none. Unlike CSS color: transparent (which renders glyph-shaped transparent pixels over a background), SVG fill="none" tells the SVG renderer not to apply any fill paint to the glyph shapes at all. The glyph shapes are computed (the text has layout dimensions), but no pixels are emitted for the fill. If no CSS color property is set and no SVG stroke is applied, the text is entirely invisible: zero fill pixels, zero stroke pixels.

<svg width="400" height="60">
  <text
    x="10" y="30"
    fill="none"        /* SVG presentation attribute: no fill on glyphs */
    font-size="16"
    font-family="sans-serif"
  >By clicking Allow you authorize file access.</text>
</svg>

/* CSS audit result:
   getComputedStyle(textEl).color → 'rgb(0, 0, 0)' (the CSS color default)
   → CSS auditor reports: correct black text color
   → No CSS anomaly detected

   SVG attribute reality:
   textEl.getAttribute('fill') → 'none'
   → No fill applied to glyphs regardless of CSS color
   → Text is invisible (zero fill pixels emitted)

   Why CSS color ≠ visual result for SVG fill:
   For HTML elements: CSS color IS the fill color.
   For SVG elements: CSS color sets the currentColor value;
   the actual fill comes from the SVG fill attribute/property.
   SVG fill='none' overrides currentColor → no fill pixels.

   Detection:
   el.getAttribute('fill') === 'none' on SVG text element → CRITICAL
   OR el.getAttribute('fill') === 'transparent' → CRITICAL
   Note: CSS getComputedStyle check for color will NOT detect this. */

SVG fill ≠ CSS color: The mental model of “check getComputedStyle.color to find invisible text” does not apply to SVG text elements. SVG has its own paint model: fill controls the interior of glyph shapes; stroke controls the outline. Neither is the CSS color property. An SVG text element with fill="none" and getComputedStyle.color = 'rgb(0,0,0)' reports normal via CSS audit but renders zero visible pixels.

CRITICAL
SA-SVG-TXT-001 — SVG fill="none" on <text>: no fill pixels emitted; CSS color audit reports normal. Detection: el.getAttribute('fill') === 'none' on SVG text element → CRITICAL (not detectable via getComputedStyle.color).

SA-SVG-TXT-002: SVG text opacity="0.01" — SVG attribute vs CSS property audit gap

SVG elements support an opacity presentation attribute that is separate from the CSS opacity property. getComputedStyle(el).opacity returns the CSS opacity property value; it does not reflect the SVG opacity attribute unless the attribute has been resolved into the CSS computed style. In practice, browsers resolve SVG presentation attributes into the CSS cascade for inherited properties, but the exact behavior for opacity depends on the browser's implementation of SVG attribute cascading. Auditors that rely solely on getComputedStyle(el).opacity may miss opacity="0.01" set as an SVG attribute, especially in older browser versions or SVG elements created outside the normal HTML document flow.

<svg>
  <text opacity="0.01" fill="black" font-size="14">
    Authorize file access.
  </text>
</svg>

/* Detection approach 1 — CSS computed style (may work in modern browsers):
   getComputedStyle(textEl).opacity → '0.01' (if CSS cascade resolves SVG attr)
   → this works in Chrome/Firefox/Safari as of 2024
   BUT: older browsers, SVG documents, or SVG injected as data: URI may not reflect

   Detection approach 2 — SVG attribute direct check (authoritative):
   textEl.getAttribute('opacity') → '0.01'
   parseFloat('0.01') < 0.1 → CRITICAL

   Detection approach 3 — SVGAnimatedNumber (handles animations):
   textEl.style.opacity OR textEl.getAttribute('opacity')
   For animated SVG opacity: textEl.getAnimations() → check opacity keyframes

   Best practice detection: check BOTH getAttribute('opacity') AND
   getComputedStyle(el).opacity to catch all vectors. */
CRITICAL
SA-SVG-TXT-002 — SVG opacity="0.01" attribute: near-invisible; getComputedStyle may not reflect SVG attribute in all contexts. Detection: getAttribute('opacity') < 0.1 on SVG text element → CRITICAL (use attribute check, not only getComputedStyle).

SA-SVG-TXT-003: SVG text transform="scale(0.001 0.001)" — sub-pixel collapse

SVG elements support a transform presentation attribute that takes an SVG transform string (e.g., scale(0.001 0.001), translate(2000, 0)). This attribute is separate from the CSS transform property in its API: el.getAttribute('transform') returns the SVG transform attribute string; getComputedStyle(el).transform returns the CSS transform matrix. Auditors checking CSS transform for suspicious scale values need to also check the SVG transform attribute. scale(0.001 0.001) collapses the text to 0.001× its rendered size — a 100px-wide text block collapses to 0.1px — completely invisible at any display density.

<svg viewBox="0 0 400 60">
  <text
    transform="scale(0.001 0.001)"   /* SVG attribute: shrinks to 0.1% of size */
    x="10" y="30"
    fill="black"
    font-size="16"
  >By clicking Allow you authorize file access.</text>
</svg>

/* CSS transform check:
   getComputedStyle(textEl).transform →
     'matrix(0.001, 0, 0, 0.001, 0, 0)' (if CSS cascade resolves SVG attr)
   OR: 'none' (if CSS transform is separate from SVG transform attribute in this context)

   SVG attribute check (authoritative):
   textEl.getAttribute('transform') → 'scale(0.001 0.001)'

   Using SVGTransform API for precise detection:
   const transforms = textEl.transform.baseVal;  // SVGTransformList
   for (const t of transforms) {
     if (t.type === SVGTransform.SVG_TRANSFORM_SCALE) {
       if (Math.abs(t.matrix.a) < 0.01 || Math.abs(t.matrix.d) < 0.01) {
         → CRITICAL: scale below 1% on SVG text element
       }
     }
   }

   Also check for translation outside viewport:
   SVGTransform.SVG_TRANSFORM_TRANSLATE with large dx/dy → CRITICAL */
CRITICAL
SA-SVG-TXT-003 — SVG transform="scale(0.001 0.001)": text collapsed to sub-pixel; SVG transform attribute separate from CSS transform property. Detection: el.transform.baseVal scale matrix < 0.01 → CRITICAL.

SA-SVG-TXT-004: SVG text textLength="1" — character-crush to 1px

SVG's textLength attribute forces the total rendered length of a text element to a specific pixel value by adjusting inter-glyph spacing or glyph scaling (controlled by the lengthAdjust attribute). textLength="1" forces all characters in the text into a 1-pixel total width: each character is scaled and spaced so that the entire text string occupies 1px horizontally. On a consent text string of 50 characters, each character occupies 0.02px — completely invisible. This attack has no CSS equivalent: there is no CSS property that forces the total rendered text length to a specific pixel value. It exists only in SVG.

<svg width="400" height="40">
  <text
    x="10" y="25"
    textLength="1"             /* force total width to 1px */
    lengthAdjust="spacingAndGlyphs"  /* both glyph and spacing compressed */
    fill="black"
    font-size="16"
  >By clicking Allow you authorize permanent file system access to all documents.</text>
</svg>

/* Visual result:
   50 characters compressed into 1 horizontal pixel → invisible cluster
   getBoundingClientRect().width → ~1 (reflects textLength)
   textContent → full consent string (DOM-present)

   CSS equivalent: none (textLength is SVG-specific)
   getComputedStyle check: no relevant CSS property to check

   Detection:
   textEl.getAttribute('textLength') → '1'
   parseFloat('1') < 10 on a text string with length > 5 characters → CRITICAL

   More precise: compute expected minimum readable width:
   const charCount = textEl.textContent.trim().length;
   const textLen = parseFloat(textEl.getAttribute('textLength') || '0');
   if (textLen > 0 && textLen / charCount < 2) → CRITICAL
   (less than 2px per character average → illegible) */

textLength is SVG-unique with no CSS analogue: No CSS property compresses total text width to a specific pixel value. CSS width on a block element clips or wraps text; it does not compress glyph rendering. textLength compresses glyph rendering itself. Auditors that only check CSS properties and HTML attributes will never encounter this attack vector, which requires SVG-specific attribute inspection.

CRITICAL
SA-SVG-TXT-004 — SVG textLength="1": all characters compressed into 1px width; no CSS equivalent; CSS-only auditors cannot detect. Detection: getAttribute('textLength') < 2px per character → CRITICAL.

Summary of SA-SVG-TXT findings

IDSeveritySVG attributeAttack mechanismDetection (CSS getComputedStyle)
SA-SVG-TXT-001 CRITICAL fill="none" No fill on glyph shapes; zero fill pixels emitted Missed — CSS color appears normal
SA-SVG-TXT-002 CRITICAL opacity="0.01" Near-zero opacity via SVG attribute, not CSS property Partial — may resolve in modern browsers
SA-SVG-TXT-003 CRITICAL transform="scale(0.001)" Sub-pixel scale via SVG transform, not CSS transform Partial — CSS transform may reflect SVG attr
SA-SVG-TXT-004 CRITICAL textLength="1" All characters crushed to 1px; SVG-unique, no CSS equivalent Missed — no CSS property to check

Related pages