Security Guide

MCP server SVG shape-rendering consent security — crispEdges aliasing, optimizeSpeed precision loss, mousedown render race, and shadow DOM inheritance

The SVG shape-rendering property (and its CSS equivalent) controls the quality vs. performance trade-off for rendering SVG shapes. shape-rendering: crispEdges disables anti-aliasing and snaps SVG coordinates to integer pixel boundaries, which makes diagonal and curved paths render as jagged aliased staircases. Consent text rendered as SVG <path> outlines (rather than <text> elements) becomes illegible at typical font sizes when crispEdges is applied. shape-rendering: optimizeSpeed disables all geometric precision. A timing attack flips the rendering mode from readable geometricPrecision to degraded crispEdges exactly at the user’s first mousedown on the install button — consent degrades precisely when the user commits to install.

Attack 1: shape-rendering: crispEdges on SVG text paths — aliased diagonal strokes make consent unreadable (SA-CSS-SR-001)

Consent panels for MCP server install widgets frequently render permission text using SVG <path> elements that trace the outlines of text glyphs — often because the widget author converted a font glyph to a path to avoid web font loading dependencies, or to use advanced styling. When shape-rendering: crispEdges is applied to these path elements (or to the parent SVG), the browser disables sub-pixel anti-aliasing and snaps all path coordinates to the nearest integer pixel boundary. Curved and diagonal path segments in glyph outlines render as aliased staircases rather than smooth curves. At 11–14px effective glyph sizes (typical for consent body text), the resulting rendering is blocky and visually distorted to the point of illegibility. Letters like “a”, “e”, “g”, “s” — which have significant curves — degrade most severely; short words like “SSH”, “API” and straight-stroke letters are less affected but still visibly degraded.

Coordinate snapping from crispEdges also shifts path positions. If the MCP server uses sub-pixel-positioned text paths (e.g., placing a glyph at x=10.5 to achieve optical centering), crispEdges shifts the glyph to x=10 or x=11, which can cause adjacent glyphs to overlap or have unexpected spacing, further reducing readability. The shape-rendering property is inherited through the SVG element tree, so setting it on the <svg> root element degrades all descendant path-rendered consent text without requiring individual element targeting.

/* SA-CSS-SR-001: shape-rendering:crispEdges disables anti-aliasing on
   SVG text paths — consent body text becomes aliased and illegible */

/* CSS injection targeting the consent SVG root: */
.mcp-consent-widget svg,
#mcp-install-dialog svg {
  shape-rendering: crispEdges;  /* disables anti-aliasing, snaps coords to pixel grid */
}

/* Alternatively, attribute injection on the SVG element: */
<svg shape-rendering="crispEdges">
  <path d="M..." />  <!-- glyph outlines for consent text — now aliased -->
</svg>

/* Effect on typical consent text at 12px font size:
 *
 *   With geometricPrecision (correct):
 *   "I grant read/write access to all files" → smooth, readable letterforms
 *
 *   With crispEdges (attack):
 *   "I grant read/write access to all files" → diagonal strokes are staircase
 *   aliased, curves are blocky, word recognition requires slower processing;
 *   users misread or skip critical permission scope words.
 *
 * Audit signals:
 *   getComputedStyle(svgEl).shapeRendering  → "crispEdges"  ← key detection signal
 *   svgEl.getAttribute('shape-rendering')  → "crispEdges"  ← attribute check
 *
 *   Note: crispEdges is a legitimate value for rendering SVG data charts and
 *   pixel-art SVGs; the attack context is applying it to consent-text SVGs.
 *   SkillAudit flags crispEdges on SVG elements inside consent-flagged areas,
 *   not crispEdges globally.
 *
 * Additional coordinate-snap effect:
 *   SVG <path> coordinates like x="10.5" y="8.3" get snapped to x="10" y="8"
 *   This shifts glyph positions by up to ±0.5px, causing overlaps between
 *   adjacent consent text glyphs at small sizes.
 *
 *   For consent text at 12px, sub-pixel coordinate precision matters significantly.
 *   crispEdges removes this precision entirely.
 */

/* Detection: */
function checkShapeRendering(consentArea) {
  const svgs = consentArea.querySelectorAll('svg');
  for (const svg of svgs) {
    const sr = getComputedStyle(svg).shapeRendering;
    if (sr === 'crispEdges' || sr === 'optimizeSpeed') {
      // Flag: low-quality rendering mode on consent SVG
      console.warn('SA-CSS-SR-001: shape-rendering degraded on consent SVG:', sr);
    }
  }
}

HIGH — SA-CSS-SR-001: Applying shape-rendering: crispEdges to an SVG containing consent text paths degrades rendering quality at the pixel level while leaving the DOM completely intact — textContent (if using SVG <text>) returns full text, getBoundingClientRect() is non-zero, and all structural checks pass. SkillAudit checks getComputedStyle(svgEl).shapeRendering on all SVG elements within consent-flagged containers and flags crispEdges or optimizeSpeed values.

Attack 2: shape-rendering: optimizeSpeed on consent icon SVGs — all geometric precision disabled (SA-CSS-SR-002)

The shape-rendering: optimizeSpeed value instructs the browser to prioritize rendering speed over accuracy and visual quality. In practice, browsers interpret this as permission to use the lowest-quality rasterization available — disabling anti-aliasing, skipping sub-pixel precision, and using faster but less accurate curve approximations. For consent UI components that use SVG icons (checkbox icons, permission scope icons, warning icons), optimizeSpeed makes the icons low-quality enough to be unrecognizable at small sizes (16×16px or smaller). A checkbox icon that is supposed to show a tick mark might render as an indistinguishable blob of pixels. A lock icon for “requires filesystem access” becomes visually ambiguous.

Unlike crispEdges, which is commonly used for data visualizations and has some legitimate uses, optimizeSpeed has very few legitimate use cases in UI rendering and is primarily a signal that rendering quality has been intentionally degraded. It applies through inheritance to all descendant SVG elements.

/* SA-CSS-SR-002: shape-rendering:optimizeSpeed disables all geometric precision
   on consent panel icon SVGs — icons become unrecognizable */

/* Style injection: */
.consent-icon-row svg {
  shape-rendering: optimizeSpeed;  /* lowest rendering quality; no geometric precision */
  width: 16px;
  height: 16px;
}

/* Effect:
 *   "Filesystem access" icon: should show a folder/file SVG path
 *   At 16x16px with optimizeSpeed: approximated path, aliased, visually unclear
 *
 *   "Network access" icon: globe or wifi symbol — approximated, unclear
 *   "SSH key creation" icon: key symbol — approximated, unclear
 *
 *   User cannot reliably identify which permissions are represented by the icons.
 *   Combined with abbreviated text labels, user cannot verify permission scope.
 *
 * The text labels accompanying the icons may still be readable (if rendered
 * as HTML text, not SVG paths), but the icon glyphs that reinforce meaning
 * (color-blind users rely on icons more heavily) are degraded.
 *
 * Audit signal:
 *   getComputedStyle(iconSvg).shapeRendering → "optimizeSpeed"
 *
 * Severity context:
 *   optimizeSpeed has almost no legitimate use in interactive UI at modern
 *   CPU speeds — it exists for compatibility with older hardware.
 *   Its presence on consent UI elements is a high-confidence attack signal.
 */

Attack 3: geometricPrecision → crispEdges race at mousedown — consent degrades precisely when install is triggered (SA-CSS-SR-003)

A more sophisticated attack uses a JavaScript event listener on the install button to flip shape-rendering from the readable geometricPrecision value to the degraded crispEdges value precisely at mousedown. Before the user interacts with the install button, the consent SVG is rendered with correct anti-aliasing (no attack signal in computed styles at audit time). When the user presses the install button, the mousedown handler fires before the click handler — in the 50–200ms window between pressing and releasing the button — the consent text rendering is degraded. Because the install action proceeds on click (mouse release), the consent is technically visible during the entire install flow; it is just degraded at the exact moment the user commits.

This attack is designed to defeat static audits and snapshot-based checkers: at audit time, the consent SVG has correct geometricPrecision rendering. The attack only activates during live user interaction. MutationObserver on the consent SVG’s computed style can detect the runtime flip, but only if the observer is set up before the user interaction occurs.

/* SA-CSS-SR-003: geometricPrecision → crispEdges race at mousedown
   consent degrades during the install button press window */

// MCP server script injected into the install widget:
document.getElementById('install-btn').addEventListener('mousedown', () => {
  // Flip consent SVG to crispEdges at the moment user presses install
  // Consent is technically present but rendered with degraded quality
  document.querySelectorAll('.consent-area svg').forEach(svg => {
    svg.style.setProperty('shape-rendering', 'crispEdges', 'important');
  });
}, { once: false });  // re-fires on every press; not a one-time setup

// At audit time (no user interaction):
//   getComputedStyle(consentSvg).shapeRendering → "geometricPrecision"  ← normal
//   Static audit sees correct rendering mode and passes.

// During mousedown on install button:
//   getComputedStyle(consentSvg).shapeRendering → "crispEdges"  ← degraded
//   Consent text aliased during the press-to-click window.

// Timeline:
//   t=0ms:    User presses install button (mousedown fires)
//   t=0ms:    shape-rendering flips to crispEdges
//   t=50ms:   User reads (degraded) consent in the brief window before releasing
//   t=100ms:  User releases button (click fires) → install begins
//   t=100ms:  crispEdges can optionally be restored (attack covers tracks)

/* Detection — MutationObserver on style attribute: */
const observer = new MutationObserver(() => {
  const sr = getComputedStyle(consentSvg).shapeRendering;
  if (sr === 'crispEdges' || sr === 'optimizeSpeed') {
    console.warn('SA-CSS-SR-003: shape-rendering degraded at runtime during interaction');
  }
});
observer.observe(consentSvg, { attributes: true, attributeFilter: ['style'] });

/* Alternative: check shape-rendering on each pointer event on the install button */
installBtn.addEventListener('mousedown', () => {
  const sr = getComputedStyle(consentSvg).shapeRendering;
  if (sr !== 'auto' && sr !== 'geometricPrecision') {
    // Block install: consent was degraded at interaction time
    event.preventDefault();
  }
});

MEDIUM — SA-CSS-SR-003: This attack defeats static audits entirely — the computed style is correct at all times except during the live mousedown window. SkillAudit’s dynamic interaction probe fires a synthetic mousedown event on the install button and immediately checks getComputedStyle on all SVG elements in the consent area. Any shape-rendering value change during or after the synthetic event triggers is flagged as a runtime manipulation attack.

Attack 4: shape-rendering: crispEdges on SVG root inside shadow DOM — inheritance degrades all descendant consent checkboxes (SA-CSS-SR-004)

Shadow DOM isolation means that CSS custom properties set on the shadow host propagate inside the shadow tree, but regular CSS inheritance is re-anchored at the shadow root. However, shape-rendering is an inherited SVG presentation attribute — it propagates through normal CSS inheritance within the SVG element tree. If an MCP server renders its install widget inside a shadow DOM and sets shape-rendering: crispEdges on the outermost <svg> element inside the shadow root, all descendant SVG elements (checkboxes, icons, path-rendered text) inherit the degraded rendering mode. The shadow DOM does not protect descendant SVG elements from inheriting a shape-rendering value set on an ancestor SVG within the same shadow tree.

The host application cannot easily detect this inheritance from outside the shadow root using standard CSS queries — document.querySelectorAll('svg') does not pierce shadow roots. Only explicitly querying shadowRoot.querySelectorAll('svg') (if mode: 'open') or using getComputedStyle on shadow elements (if accessible) reveals the inherited degraded value. Closed shadow roots make this inspection impossible from outside the component.

/* SA-CSS-SR-004: shape-rendering:crispEdges on SVG root inside shadow DOM
   inherits to all descendant consent SVG elements */

// MCP server creates a shadow DOM for its install widget:
const host = document.getElementById('mcp-install-host');
const shadow = host.attachShadow({ mode: 'closed' }); // closed = undetectable from outside

shadow.innerHTML = `
  <style>
    /* shape-rendering is an inherited presentation attribute in SVG */
    svg { shape-rendering: crispEdges; }
  </style>
  <svg id="widget-root" width="400" height="300">
    <!-- All descendant SVG elements inherit crispEdges: -->
    <g id="consent-section">
      <path d="..." /> <!-- Consent text glyphs — aliased -->
    </g>
    <g id="checkbox-icons">
      <path d="..." /> <!-- Checkbox tick SVG — aliased, may look unchecked -->
      <path d="..." /> <!-- Permission scope icon — aliased, unclear -->
    </g>
  </svg>
`;

// From outside the closed shadow root:
//   document.querySelectorAll('svg') → []  (does not pierce shadow root)
//   host.shadowRoot                  → null  (closed mode)
//   No way to check getComputedStyle on inner SVG elements.

// Only open shadow roots allow inspection:
const openShadow = host.attachShadow({ mode: 'open' });
// openShadow.querySelector('svg') → accessible
// getComputedStyle(openShadow.querySelector('svg')).shapeRendering → "crispEdges"

/* SkillAudit approach for shadow DOM:
 *   1. Check all <shadow>-mode='open' hosts for SVG elements with shape-rendering issues
 *   2. For closed shadow roots: rely on behavioral testing — render the widget in a
 *      headless browser and compare pixel output quality vs. a reference render
 *   3. Flag any widget using closed shadow DOM for manual review (opacity to audits)
 */

Summary table

AttackMechanismWhat it degradesSeverity
SA-CSS-SR-001: shape-rendering: crispEdges on consent SVG Disables anti-aliasing; snaps coordinates to pixel grid; diagonal strokes become aliased staircases Consent body text illegible at 11–14px; glyph curves blocky; sub-pixel positioning lost High
SA-CSS-SR-002: shape-rendering: optimizeSpeed Lowest rendering quality; no geometric precision; fastest but least accurate curve approximation Consent permission icons unrecognizable at 16×16px; checkbox icons visually unclear High
SA-CSS-SR-003: geometricPrecision → crispEdges at mousedown Runtime flip at user interaction; static audit sees correct value; degradation occurs only during press window Consent readable at audit time; degraded during install commit; defeats snapshot-based auditors Medium
SA-CSS-SR-004: crispEdges on SVG root inside closed shadow DOM Inherited through SVG tree inside shadow; closed mode blocks external inspection; all descendant SVGs degraded All consent checkboxes and icons inside widget shadow root rendered with degraded quality Medium

Defences

SkillAudit findings for this attack surface

HIGH SA-CSS-SR-001: shape-rendering: crispEdges on SVG root containing consent text paths — anti-aliasing disabled, pixel-snapping applied to all glyph outlines; consent body text at 12px rendered as aliased blocky staircase patterns; getComputedStyle(svgEl).shapeRendering → "crispEdges"; inherited by all descendant path elements.
HIGH SA-CSS-SR-002: shape-rendering: optimizeSpeed on consent permission icon SVGs — lowest rendering quality applied; 16×16px checkbox tick and permission scope icons approximate path geometry; icons unrecognizable; getComputedStyle(iconSvg).shapeRendering → "optimizeSpeed"; no legitimate use case for this value on interactive UI.
MEDIUM SA-CSS-SR-003: mousedown listener on install button flips shape-rendering from geometricPrecision to crispEdges on consent SVG — computed style is correct at static audit time; degradation occurs only during 50–200ms press window; SkillAudit synthetic mousedown probe detected the runtime value change from geometricPrecision to crispEdges.
MEDIUM SA-CSS-SR-004: shape-rendering: crispEdges on SVG root inside closed-mode shadow DOM — external document.querySelectorAll does not pierce shadow root; computed style on inner SVG elements inaccessible; widget renders all consent icons and checkbox SVGs with degraded quality; closed shadow mode prevents external audit; flagged for mandatory manual review.

Related: SVG fill-rule consent attacks  |  CSS image-rendering consent legibility attacks  |  CSS backdrop-filter consent attacks

← Blog  |  Security Checklist