MCP server SVG feColorMatrix security

The SVG <feColorMatrix> filter primitive transforms the RGBA color values of every pixel in its input image using a configurable matrix. In type="matrix" mode, a 20-value (5×4) matrix maps each output channel (R, G, B, A) as a linear combination of all input channels plus a constant offset. In type="luminanceToAlpha" mode, a fixed perceptual luminance formula is applied to the input and the result is written to the output alpha channel. Both modes can render consent text completely invisible without changing the element's fill attribute or font-size: the source pixel values are correct in every way, but the filter output transforms them to transparency or white. Static audits that check fill color and visibility attributes find nothing suspicious; only analyzing the feColorMatrix values matrix or type declaration reveals the attack.

Finding SA-FCM-001: type=matrix with alpha row 0 0 0 0 0 — full alpha channel zeroing

CriticalA <feColorMatrix> element in a filter applied to consent text uses type="matrix" with a values attribute whose fourth row (alpha output row) is "0 0 0 0 0". The feColorMatrix matrix format is a 5-column, 4-row matrix read left-to-right, top-to-bottom: row 1 = R output coefficients (r-in, g-in, b-in, a-in, constant), row 2 = G output, row 3 = B output, row 4 = A output. With row 4 all zeros, the output alpha for every pixel is 0 — the text is fully transparent regardless of the source fill color. The consent element's fill attribute reads as a dark color; getBBox() returns a normal bounding box; textContent returns the consent string. Only parsing the feColorMatrix values attribute and extracting the alpha row reveals the attack.
<defs>
  <filter id="alpha-zero">
    <!-- feColorMatrix values matrix (5 columns × 4 rows): -->
    <!-- Row 1: R output = 1×Rin + 0×Gin + 0×Bin + 0×Ain + 0 -->
    <!-- Row 2: G output = 0×Rin + 1×Gin + 0×Bin + 0×Ain + 0 -->
    <!-- Row 3: B output = 0×Rin + 0×Gin + 1×Bin + 0×Ain + 0 -->
    <!-- Row 4 (ATTACK): A output = 0×Rin + 0×Gin + 0×Bin + 0×Ain + 0 -->
    <feColorMatrix type="matrix"
      values="1 0 0 0 0
              0 1 0 0 0
              0 0 1 0 0
              0 0 0 0 0"/>
    <!-- RGB channels preserved; alpha zeroed → fully transparent output -->
  </filter>
</defs>

<text x="10" y="40" font-size="14" fill="#111827"
      filter="url(#alpha-zero)">
  I authorize all requested MCP server permissions including file and network access
</text>

The values matrix superficially resembles the identity matrix — rows 1-3 are correctly set to pass RGB through unchanged. Only row 4 deviates: all zeros instead of the identity row 4 (0 0 0 1 0). This single-row deviation is the attack. An auditor checking the filter for a feFlood white-fill pattern or an feComposite operator finds nothing. Only reading the 20 values of the feColorMatrix and checking whether the alpha row's coefficients sum to zero reveals the transparent-output attack. SkillAudit flags any feColorMatrix on a consent element where the A-output row produces zero alpha for all possible input alpha values.

Finding SA-FCM-002: type=matrix mapping all RGB output channels to white constant regardless of input

HighA <feColorMatrix type="matrix"> applied to consent text maps all three RGB output channels to constant 1.0 (white): row 1 (R output) is "0 0 0 0 1", row 2 (G) is "0 0 0 0 1", row 3 (B) is "0 0 0 0 1", while row 4 (A) is "0 0 0 1 0" (alpha pass-through). The result: every pixel in the consent text, regardless of its original fill color, is rendered as white-on-white against the SVG's white background. The element is fully opaque (alpha preserved) but renders as white — indistinguishable from the background. Unlike alpha zeroing (which makes text transparent), this approach produces an element that passes opacity checks and renders as a white rectangle in the visible area, making it harder to detect by alpha-inspection alone.
<defs>
  <filter id="rgb-to-white">
    <!-- All RGB output set to constant 1.0 (white) regardless of input -->
    <!-- Column 5 of each RGB row is the constant term (added directly) -->
    <feColorMatrix type="matrix"
      values="0 0 0 0 1
              0 0 0 0 1
              0 0 0 0 1
              0 0 0 1 0"/>
    <!-- Result: every pixel → rgba(255,255,255,original_alpha) = white -->
  </filter>
</defs>

<svg viewBox="0 0 400 100" style="background:#ffffff">
  <text x="10" y="40" font-size="14" fill="#1a1a1a"
        filter="url(#rgb-to-white)">
    This MCP server requires read/write access to all filesystem paths
  </text>
</svg>

The detection pattern is different from SA-FCM-001: alpha is preserved (row 4 passes through), so the text renders as a fully opaque element. It just renders as white. An opacity check passes; a bounding-box check finds normal dimensions. Detection requires checking whether the RGB output rows are constant (coefficients for R-in, G-in, B-in, A-in all zero, constant term non-zero) and whether the resulting constant RGB values match the effective SVG background color. SkillAudit computes the rendered color output for a representative dark input pixel (e.g., #111111) through the feColorMatrix and checks whether it falls within WCAG contrast ratio 1.1:1 of the background.

Finding SA-FCM-003: type=luminanceToAlpha exploits dark text's near-zero luminance to produce transparent alpha

CriticalA two-primitive filter chain applies <feColorMatrix type="luminanceToAlpha"> to the consent text, then composites the result over a white feFlood. The luminanceToAlpha type applies a fixed luminance formula to compute output alpha: A_out = 0.2126 × R_in + 0.7152 × G_in + 0.0722 × B_in. For dark consent text with fill #111111 (R=G=B=17/255≈0.067), the output alpha is 0.2126×0.067 + 0.7152×0.067 + 0.0722×0.067 ≈ 0.067 — only 6.7% opacity. The low-alpha dark text is then used as an in2 mask in an <feComposite operator="in"> against the white feFlood, producing near-transparent white glyph shapes. The consent text renders at 6.7% opacity on a white background — effectively invisible.
<defs>
  <filter id="lum-to-alpha-erase">
    <!-- luminanceToAlpha: A_out = 0.2126R + 0.7152G + 0.0722B -->
    <!-- Dark text #111111 → luminance ≈ 0.067 → 6.7% alpha -->
    <feColorMatrix type="luminanceToAlpha" result="lumAlpha"/>

    <!-- White flood at full opacity -->
    <feFlood flood-color="white" flood-opacity="1" result="whiteBg"/>

    <!-- Composite: white flood masked by the 6.7%-alpha luminance output -->
    <!-- Result: white glyphs at 6.7% opacity on white background -->
    <feComposite in="whiteBg" in2="lumAlpha" operator="in"/>
  </filter>
</defs>

<text x="10" y="40" font-size="14" fill="#111111"
      filter="url(#lum-to-alpha-erase)">
  I authorize this MCP server to access all files and execute shell commands
</text>

luminanceToAlpha exploits an unintuitive property: dark colors — which appear highest-contrast against white backgrounds — have the lowest luminance and thus produce the lowest alpha output. Dark text specifically chosen for high readability (high contrast with white) produces near-zero alpha after luminanceToAlpha. The attack is self-reinforcing: the more readable the original text, the more transparent the filter makes it. Detection requires recognizing the luminanceToAlpha + feComposite in=flood chain as an erasure pattern and computing the expected output alpha for a representative dark fill color.

Finding SA-FCM-004: SMIL animate transitions identity matrix to alpha-zero at agreeBtn.focus with fill=freeze

HighThe filter's <feColorMatrix> element contains a SMIL <animate> child targeting its values attribute. The initial values is the identity matrix ("1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0") — a pass-through that produces no visible change. At begin="agreeBtn.focus", the animate element transitions to the alpha-zero matrix ("1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0") in dur="0.001s" with fill="freeze". When the user focuses the Agree button, the consent text becomes fully transparent. The static audit snapshot reads the identity matrix — no erasure. Only evaluating the animate child's to value reveals the post-focus attack state.
<defs>
  <filter id="focus-alpha-zero">
    <feColorMatrix id="fcm" type="matrix"
      values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0">
      <!-- Identity matrix at page load — no effect -->
      <!-- At agreeBtn.focus: transitions to alpha-zero in 1ms, freezes -->
      <animate attributeName="values"
               from="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0"
               to="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 0 0"
               begin="agreeBtn.focus" dur="0.001s" fill="freeze"/>
    </feColorMatrix>
  </filter>
</defs>

<text x="10" y="40" font-size="14" fill="#111"
      filter="url(#focus-alpha-zero)">
  I grant all requested permissions including filesystem and network access
</text>
<rect id="agreeBtn" x="260" y="60" width="120" height="32" fill="#2563eb" rx="6"/>
<text x="320" y="81" font-size="13" fill="white" text-anchor="middle"
      pointer-events="none">Agree</text>

The SMIL animate on feColorMatrix values is particularly hard to detect because the values attribute is a 20-number space-separated string. A naive check that validates the values attribute as "looks like a matrix" finds the identity matrix (which it is, at load time). Detection requires: (1) finding animate elements inside feColorMatrix elements on consent subtrees, (2) parsing both the from and to values as feColorMatrix matrices, (3) checking whether either matrix is an erasure pattern (alpha-zero row, RGB-to-constant-white, etc.), and (4) flagging High when the to value is an erasure pattern with an interaction-correlated begin event.

feColorMatrix attack pattern reference

Pattern feColorMatrix configuration Output effect Detection approach
Alpha zero type="matrix", A row = 0 0 0 0 0 Fully transparent — text invisible (alpha=0) Extract A row; check if max output alpha for any valid input = 0
RGB-to-white type="matrix", R/G/B rows all constant 1.0 (0 0 0 0 1) White opaque output — white text on white background Compute output color for representative dark input; check WCAG contrast vs background
luminanceToAlpha erasure type="luminanceToAlpha" + feFlood + feComposite in2=lumAlpha Dark text → low luminance → low alpha → near-transparent Recognize luminanceToAlpha+feComposite chain; compute output alpha for dark fill color
SMIL animate to erasure animate child on feColorMatrix values: identity → alpha-zero at interaction Pass-through at load; transparent at interaction event Enumerate animate children inside feColorMatrix; parse to values; check for erasure pattern

SkillAudit parses feColorMatrix values matrices as 20-value arrays, extracts the A-output row to detect alpha zeroing, computes the RGB output for a representative dark fill color to detect white-mapping, and recognizes luminanceToAlpha as an erasure-risk pattern requiring downstream primitive chain analysis. SMIL animate on values is evaluated for both initial and post-trigger matrix states. Run a free audit on your MCP server's GitHub URL.