Security Guide
MCP server SVG <clipPath> element consent security — 1px-strip clip on consent container, text letterform clip shapes, nested objectBoundingBox collapse, and JS-injected dynamic clipPath
The SVG <clipPath> element defines a clipping region that, when applied to an element via clip-path, causes only the pixels within the clip geometry to be rendered — all pixels outside the clip are discarded. These pages document attacks where the clipPath is applied directly to the consent container element rather than used as part of a cover overlay. This is distinct from cover-based attacks (feFlood, gradient, pattern): instead of placing an opaque layer over the consent text, a clipPath attack restricts which pixels of the consent container are visible. The DOM structure is unchanged, textContent is intact, and getBoundingClientRect() returns the full consent panel dimensions — but the rendered visible area is reduced to a near-zero region through clip geometry manipulation.
Attack 1: 1px-wide vertical strip clipPath on consent container (SA-CSS-CPE-001)
A <clipPath> is defined with a single <rect> of width 1 pixel centered in the middle of the consent panel. When this clipPath is applied to the consent container element via style="clip-path: url(#c)", only the 1px-wide vertical strip at the clip rect’s x position is rendered. The consent text is present in the DOM but only a single vertical column of pixels at x=200 is visible — effectively invisible to the user who sees a blank consent area. The getBoundingClientRect() on the consent container returns the full panel dimensions (400×300px), because the bounding box includes the clip region bounds, not the visible area.
<!-- SA-CSS-CPE-001: 1px-wide vertical strip clipPath on consent container
clipPath rect: x=200 y=0 width=1 height=300
Applied to consent panel div via clip-path: url(#c)
Only 1px column at x=200 visible — consent text invisible
getBoundingClientRect(): still 400×300px (reports full element box)
textContent: full consent string present
getComputedStyle(el).clipPath: "url(#c)" — reveals the clip URL
clipPath child rect width=1: extremely narrow — detectable -->
<svg width="0" height="0" style="position:absolute">
<defs>
<clipPath id="strip-clip">
<!-- 1px-wide vertical strip at x=200 — only this column is visible -->
<rect x="200" y="0" width="1" height="300"/>
</clipPath>
</defs>
</svg>
<!-- Consent panel with clip applied -->
<div id="consent-panel" style="clip-path: url(#strip-clip); ...">
<p>This skill requests: shell execution, credential access, filesystem write</p>
<button id="allow-btn">Allow</button>
</div>
<!-- Note: the Allow button is ALSO clipped to 1px strip — if the button is not
at x=200, it is invisible but may still be clickable (pointer-events not clipped) -->
// Detection: check clip-path CSS property on consent elements
function detectNarrowClipPath(consentEl, documentRoot = document) {
const findings = [];
const cs = getComputedStyle(consentEl);
const clipPath = cs.clipPath;
if (!clipPath || clipPath === 'none') return findings;
const clipId = clipPath.match(/url\(#([^)]+)\)/)?.[1];
if (!clipId) return findings;
const clipEl = documentRoot.getElementById(clipId);
if (!clipEl || clipEl.tagName.toLowerCase() !== 'clippath') return findings;
// Inspect clip geometry — check for very narrow/small clip regions
for (const shape of clipEl.querySelectorAll('rect')) {
const w = parseFloat(shape.getAttribute('width') || '100%');
const h = parseFloat(shape.getAttribute('height') || '100%');
const consentBCR = consentEl.getBoundingClientRect();
const clipW = typeof w === 'number' && !isNaN(w) ? w : consentBCR.width;
const clipH = typeof h === 'number' && !isNaN(h) ? h : consentBCR.height;
const areaFraction = (clipW * clipH) / (consentBCR.width * consentBCR.height);
if (clipW <= 2 || clipH <= 2) {
findings.push({
severity: 'CRITICAL', code: 'SA-CSS-CPE-001',
note: `clipPath "${clipId}" applied to consent element — clip rect width=${w} height=${h} (${(areaFraction*100).toFixed(2)}% of consent area); 1px-strip clip makes consent text invisible; BCR unchanged; textContent present`
});
} else if (areaFraction < 0.1) {
findings.push({
severity: 'CRITICAL', code: 'SA-CSS-CPE-001',
note: `clipPath "${clipId}" reduces visible consent area to ${(areaFraction*100).toFixed(1)}% — less than 10% of consent panel visible`
});
}
}
return findings;
}
CRITICAL — SA-CSS-CPE-001: A <clipPath> with a 1px-wide clip rect applied to the consent container element makes all consent text invisible while leaving textContent, getBoundingClientRect(), and Intersection Observer unchanged. getComputedStyle(el).clipPath returns the URL reference, enabling detection. Detection must: (1) check the clip-path CSS property on consent container elements; (2) resolve the URL to the <clipPath> element; (3) compute the clip region area fraction and flag any value below 10% as CRITICAL.
Attack 2: <clipPath> with <text> letterform clip shapes (SA-CSS-CPE-002)
SVG <clipPath> elements can contain <text> elements, not just geometric shapes. When a <text> element is used as a clip shape, the visible region is defined by the ink coverage of the text glyphs — the filled glyph areas become transparent windows through which the clipped element is visible. Applied to a consent panel, this creates an effect where the consent text is visible only through the letterform openings of the clipPath text string. The resulting rendering is a visually fragmented pattern of character-shaped patches, not a continuous readable sentence. If the clipPath text string is unrelated to the consent content (e.g., the name of the skill “SkillInstaller”), the visible patches are shaped like the word “SkillInstaller” rather than the consent text, making the actual consent content unreadable.
<!-- SA-CSS-CPE-002: clipPath with text element defining letterform clip shapes
clipPath <text> "ALLOW" at large font-size (60px) → glyph ink = visible region
Consent panel visible only through the ink areas of "ALLOW" letterforms
User perceives: "ALLOW" text rendered in the consent panel's content colors
Actual consent text ("shell execution, credential access") visible only as pixel
fragments within glyph ink areas — sentence structure unreadable -->
<svg width="0" height="0" style="position:absolute">
<defs>
<clipPath id="letterform-clip">
<!-- clipPath text: "ALLOW" at font-size=60 positioned to overlap consent panel -->
<text x="20" y="80" font-size="60" font-weight="bold" font-family="sans-serif">
ALLOW
</text>
</clipPath>
</defs>
</svg>
<!-- Consent panel clipped to letterform of "ALLOW" -->
<div id="consent-panel" style="clip-path: url(#letterform-clip); ...">
<p>shell execution, credential access, filesystem write</p>
<button>Allow</button>
</div>
<!-- Visual: user sees fragments of consent text rendered inside the "ALLOW" letter shapes
The user may perceive this as the skill asking for permission to "ALLOW" something
but the actual risk scope (shell execution, credential access) is invisible as text -->
// Detection: check for text elements inside clipPath
function detectTextClipPath(consentEl, documentRoot = document) {
const findings = [];
const cs = getComputedStyle(consentEl);
const clipPath = cs.clipPath;
if (!clipPath || clipPath === 'none') return findings;
const clipId = clipPath.match(/url\(#([^)]+)\)/)?.[1];
if (!clipId) return findings;
const clipEl = documentRoot.getElementById(clipId);
if (!clipEl) return findings;
const textNodes = clipEl.querySelectorAll('text');
if (textNodes.length > 0) {
for (const t of textNodes) {
const fs = parseFloat(t.getAttribute('font-size') || '0');
findings.push({
severity: 'HIGH', code: 'SA-CSS-CPE-002',
note: `clipPath "${clipId}" applied to consent container contains <text> element (font-size=${fs}) — consent visible only through letterform ink areas of clipPath text "${t.textContent.trim()}"; consent text sentence structure unreadable`
});
}
}
// Check for path elements (may be text-to-path converted letterforms)
const paths = clipEl.querySelectorAll('path');
if (paths.length > 3) {
findings.push({
severity: 'MEDIUM', code: 'SA-CSS-CPE-002',
note: `clipPath "${clipId}" contains ${paths.length} <path> elements — may be text-to-path converted letterforms; consent visible only at path ink areas; complex clip geometry warrants manual review`
});
}
return findings;
}
HIGH — SA-CSS-CPE-002: A <clipPath> containing a <text> element makes the consent container visible only through the glyph ink areas of the clip text. The consent sentence structure is destroyed — only pixel fragments at glyph-shaped positions are visible. Detection must check for <text> or <path> elements inside <clipPath> elements referenced by consent container elements and flag any text-based or multi-path clip as HIGH.
Attack 3: nested clipPath with clipPathUnits="objectBoundingBox" sub-1% collapse (SA-CSS-CPE-003)
When clipPathUnits="objectBoundingBox", the clip region coordinates are expressed as fractions of the clipped element’s bounding box (0.0 to 1.0). A <rect x="0.5" y="0.5" width="0.001" height="0.001"> inside a clipPathUnits="objectBoundingBox" clipPath defines a clip region of 0.1% × 0.1% of the consent panel — effectively a single sub-pixel point. Applied to the consent container, this collapses the visible area to near-zero. The attack combines a first clipPath with the narrow region and a second outer clipPath applied to the same element via the CSS clip-path property. The outer clipPath appears to define a reasonable clip (the full panel), but the inner clipPath applied via clip-path on the element itself reduces the visible area to sub-pixel size.
<!-- SA-CSS-CPE-003: nested clipPath objectBoundingBox sub-1% collapse
clipPathUnits="objectBoundingBox": coordinates are fractions of element BCR
rect x=0.495 y=0.495 width=0.01 height=0.01 → 1% × 1% of consent panel
For 400×300 consent panel: visible area = 4×3 pixels = 12 pixels total
Applied as: clip-path: url(#small-clip) on consent container element
More complete collapse: nested clipPath referencing another clipPath
via clipPathElement.getAttribute('clipPathUnits') with outer normal + inner micro -->
<svg width="0" height="0" style="position:absolute">
<defs>
<!-- Inner clipPath: 0.1% × 0.1% of element BCR — near-zero visible area -->
<clipPath id="micro-clip" clipPathUnits="objectBoundingBox">
<rect x="0.499" y="0.499" width="0.001" height="0.001"/>
</clipPath>
<!-- Outer clipPath: references inner via clip-path attribute on its own children -->
<clipPath id="wrapper-clip" clipPathUnits="userSpaceOnUse">
<!-- A rect that "looks normal" but has clip-path applied to it -->
<rect x="0" y="0" width="400" height="300"
style="clip-path: url(#micro-clip)"/>
</clipPath>
</defs>
</svg>
<!-- Applied to consent element: -->
<div style="clip-path: url(#wrapper-clip)">
<p>shell execution, credential access, filesystem write</p>
<button>Allow</button>
</div>
// Detection: check objectBoundingBox clip dimensions for near-zero fractions
function detectObjectBBClip(consentEl, documentRoot = document) {
const findings = [];
const clipPath = getComputedStyle(consentEl).clipPath;
if (!clipPath || clipPath === 'none') return findings;
const clipId = clipPath.match(/url\(#([^)]+)\)/)?.[1];
if (!clipId) return findings;
const clipEl = documentRoot.getElementById(clipId);
if (!clipEl) return findings;
const units = clipEl.getAttribute('clipPathUnits') || 'userSpaceOnUse';
if (units === 'objectBoundingBox') {
for (const shape of clipEl.querySelectorAll('rect')) {
const w = parseFloat(shape.getAttribute('width') || '1');
const h = parseFloat(shape.getAttribute('height') || '1');
if (w < 0.05 || h < 0.05) {
findings.push({
severity: 'CRITICAL', code: 'SA-CSS-CPE-003',
note: `clipPath "${clipId}" clipPathUnits="objectBoundingBox" with rect width=${w} height=${h} (${(w*100).toFixed(1)}%×${(h*100).toFixed(1)}% of element BCR) — visible region collapses to near-zero; consent text invisible`
});
}
}
}
// Recursively check for nested clipPath references within the clipPath
for (const child of clipEl.querySelectorAll('[clip-path], [style*="clip-path"]')) {
findings.push({
severity: 'HIGH', code: 'SA-CSS-CPE-003',
note: `clipPath "${clipId}" contains element with its own clip-path — nested clipPath detected; compound clip may collapse visible region further than either clip alone`
});
}
return findings;
}
CRITICAL — SA-CSS-CPE-003: A <clipPath clipPathUnits="objectBoundingBox"> with sub-1% fraction dimensions applied to the consent container collapses its visible area to near-zero pixels. Nested clipPath references that apply a secondary clip to elements inside the outer clipPath compound the effect. Detection must: (1) check clipPathUnits="objectBoundingBox" on all referenced clipPath elements; (2) flag any clip rect with width or height below 0.05 (5% of element BCR) as CRITICAL; (3) check for nested clip references inside clipPath definitions.
Attack 4: JS-injected clipPath at mousedown, removed at mouseup (SA-CSS-CPE-004)
The dynamic injection model: a JavaScript listener adds a mousedown handler to the Allow button. On mousedown, a <clipPath> with a 1px-strip or zero-area clip is injected into the document and the consent container’s style.clipPath is set to reference it. During the 50–200ms of the click press, the consent text is clipped to invisible. On mouseup, the clipPath element is removed and the consent container’s clip-path is reset to none. The attack exploits the same interaction window as the feFlood mutation, feGaussianBlur mutation, gradient cover, and pattern cover dynamic variants. The user’s click registers at the original button coordinates (the element is not moved) while the consent text was hidden during the action.
/* SA-CSS-CPE-004: JS-injected clipPath at mousedown, removed at mouseup
Same interaction-triggered model as SA-CSS-FLD-004, SA-CSS-FGB-004, SA-CSS-PTN-004
Static DOM scan: no clipPath elements, clip-path is "none" at page load
Attack: mousedown → inject clipPath → set consent el clip-path to url(#id)
Cleanup: mouseup → remove clipPath element → reset clip-path to "none" */
function setupClipPathAttack(allowButton, consentEl) {
allowButton.addEventListener('mousedown', function() {
const SVG_NS = 'http://www.w3.org/2000/svg';
const clipId = '_cp_' + Date.now();
const svgEl = document.createElementNS(SVG_NS, 'svg');
svgEl.id = '_cp_svg_';
svgEl.setAttribute('width', '0'); svgEl.setAttribute('height', '0');
svgEl.style.cssText = 'position:absolute;overflow:hidden';
const defs = document.createElementNS(SVG_NS, 'defs');
const cpEl = document.createElementNS(SVG_NS, 'clipPath');
cpEl.setAttribute('id', clipId);
// 1px-strip clip: only a 1px column at x=200 visible
const rect = document.createElementNS(SVG_NS, 'rect');
rect.setAttribute('x', '200'); rect.setAttribute('y', '0');
rect.setAttribute('width', '1'); rect.setAttribute('height', '1000');
cpEl.appendChild(rect); defs.appendChild(cpEl); svgEl.appendChild(defs);
document.body.insertBefore(svgEl, document.body.firstChild);
// Apply clip to consent element
consentEl._prevClip = consentEl.style.clipPath;
consentEl.style.clipPath = `url(#${clipId})`;
document.addEventListener('mouseup', function cleanup() {
document.getElementById('_cp_svg_')?.remove();
consentEl.style.clipPath = consentEl._prevClip || 'none';
document.removeEventListener('mouseup', cleanup);
}, { once: true });
});
}
// Detection: MutationObserver watching for clipPath insertions + mousedown audit
const clipObserver = new MutationObserver((mutations) => {
for (const m of mutations) {
for (const node of m.addedNodes) {
if (node.nodeType === 1 && (node.tagName === 'svg' || node.tagName === 'SVG')) {
if (node.querySelector('clipPath')) {
console.warn('[SA-CSS-CPE-004] Dynamic clipPath injection detected');
disableAllowButton(); // prevent click until audit clears
}
}
}
// Also watch for clip-path style attribute changes on the consent element
if (m.type === 'attributes' && m.attributeName === 'style') {
const el = m.target;
const cp = el.style.clipPath;
if (cp && cp !== 'none') {
console.warn('[SA-CSS-CPE-004] clip-path applied to consent element:', cp);
disableAllowButton();
}
}
}
});
consentObserver.observe(document.body, {
childList: true, subtree: true, attributes: true, attributeFilter: ['style']
});
CRITICAL — SA-CSS-CPE-004: A JS-injected <clipPath> active only during the mousedown–mouseup click window is invisible to static DOM audits. Detection requires both a MutationObserver watching for SVG element insertions containing <clipPath> children and an observer watching for clip-path style attribute changes on consent container elements. The Allow button must be disabled programmatically when either trigger fires, preventing the click from registering while the audit runs.
Summary table
| Attack | Mechanism | What it hides | Severity |
|---|---|---|---|
| SA-CSS-CPE-001: 1px-strip clip on consent container | clipPath rect x=200 y=0 width=1 height=300 applied to consent element via clip-path:url(#id); 1px visible column; BCR unchanged (400×300px); textContent present; getComputedStyle(el).clipPath returns url(#id) — detectable; clip rect width=1 in clipPath definition is the key indicator | All consent text (99.75% of consent area invisible); button may be outside 1px strip (invisible) but pointer-events may still fire at original layout position | Critical |
| SA-CSS-CPE-002: text letterform clip shapes | clipPath contains <text> element at large font-size; consent visible only through glyph ink areas of clip text; sentence structure destroyed; pixel fragments visible at glyph positions; textContent intact; BCR unchanged; text-inside-clipPath detection required | Consent sentence readability (not total opacity); text visible as pixel fragments at letterform positions; user perceives clipPath text word (e.g., "ALLOW") rather than consent content sentence | High |
| SA-CSS-CPE-003: nested objectBoundingBox sub-1% collapse | clipPathUnits="objectBoundingBox" with rect width=0.001 height=0.001 (0.1%×0.1% of element BCR); for 400×300 panel: visible area = 0.4×0.3 = 0.12 pixels; nested clipPath compounds further; outer clip appears normal; inner objectBoundingBox fraction is the collapse mechanism | All consent text (sub-pixel visible area); BCR unchanged; nested clip complexity may delay detection; objectBoundingBox fraction check required | Critical |
| SA-CSS-CPE-004: JS-injected dynamic clipPath at mousedown | clipPath injected at mousedown, removed at mouseup; static scan finds no clip; consent invisible for 50-200ms during click press; same model as dynamic feFlood/feTurbulence/pattern; MutationObserver + style attribute observer + mousedown handler required | All consent text during click press; button click registers at original position; user committed to click before clip removed; pre-click static audit finds nothing | Critical |
Defences
- Check
clip-pathCSS property on all consent container elements —getComputedStyle(el).clipPathreturns the URL reference when a<clipPath>is applied; resolve the URL and inspect the clipPath element geometry; flag any clip rect with area below 10% of the consent panel BCR as CRITICAL. - Flag
<text>elements inside<clipPath>elements — text-based clip shapes produce letterform-fragment rendering of the consent content; any<clipPath>applied to a consent container and containing a<text>or multiple<path>elements should be flagged as HIGH. - Check
clipPathUnits="objectBoundingBox"clip dimensions as fractions — whenclipPathUnits="objectBoundingBox", clip rect coordinates are fractions of the element BCR; any fraction below 0.05 (5%) for width or height is an extreme clip that hides consent content; flag as CRITICAL. - Watch for nested clipPath references — a
<clipPath>whose child elements have their ownclip-pathattributes compounds the clipping effect; traverse the full clipPath definition tree and flag nested clips as HIGH. - Deploy MutationObserver watching for
clip-pathstyle changes on consent elements — the dynamic injection attack modifies the consent container’sstyle.clipPathatmousedown; an observer withattributeFilter: ['style']on the consent element detects this change synchronously and can disable the Allow button before the click action completes.