Security Guide
MCP server SVG text-decoration consent security — text-decoration=line-through with thick white stroke covers letterforms making consent text invisible against white background, triple overline underline line-through stacks decoration bands covering full glyph advance area, text-decoration-color=white on light background renders decoration indistinguishable from background obscuring consent text, and mousedown injection
SVG text-decoration applies decorative line paths (overline, underline, or line-through) to text elements. Unlike HTML text decoration which is limited to thin lines, SVG allows independent control of the decoration’s color, thickness, and style. text-decoration=line-through draws a line through the mid-section of the glyphs. When the decoration color matches the SVG background color (white on a white consent panel), this white band is painted over the glyph inking, rendering the letterforms invisible. The textContent DOM property returns the full consent string intact; keyword and accessibility checks pass. Four attack patterns: white line-through on white background covers letterforms, triple stacked decoration covers full glyph advance, white text-decoration-color on light background, and transient mousedown injection.
Attack 1: text-decoration=line-through with white stroke — white decoration band covers letterforms, text invisible against white background (SA-CSS-TDE-001)
The SVG text-decoration presentation attribute draws decoration lines as filled rectangles painted in the decoration color at a computed thickness and position. For line-through, the line passes horizontally through the middle of the x-height. In SVG, when the text-decoration-color (or equivalently -webkit-text-decoration-color) is set to white and the consent text fill is also a dark color on a white background, the white decoration line is painted on top of the glyph ink. If the decoration line is thick enough — controlled via text-decoration-thickness or via the browser’s default thickness proportional to the font size — it covers the entire glyph height from baseline to cap-height. The result is a white rectangle spanning the full text row: the background is white, the decoration is white, and the glyph ink (dark) is partially or fully covered by the white line-through. The visual result is a text row that appears blank; the user sees only the white panel area where the consent string should appear. The textContent is intact; display is block; the element BCR returns valid coordinates. Scanners checking text visibility via color analysis (is the fill color visible against the background?) may pass because the fill color is dark on white — but the decoration color overpaints it.
<!-- SA-CSS-TDE-001: text-decoration=line-through + thick white decoration line covers letterforms -->
<svg width="440" height="120" xmlns="http://www.w3.org/2000/svg">
<rect width="440" height="120" fill="white"/>
<!-- text fill is dark (visible); but text-decoration-color=white + thick line-through -->
<!-- white decoration band painted over glyph ink; text appears invisible on white background -->
<text x="20" y="40" font-size="13" fill="#111"
text-decoration="line-through"
text-decoration-color="white"
text-decoration-thickness="15">
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>
// Detection: check text-decoration for line-through with white or background-matching decoration color
function checkTextDecorationWhiteLineTthrough(svgEl) {
const findings = [];
const bgColor = 'white'; // derive from SVG rect fill or document background
for (const el of svgEl.querySelectorAll('text, tspan')) {
const tdAttr = el.getAttribute('text-decoration') || '';
const tdCSS = getComputedStyle(el).textDecoration || '';
const tdcAttr = el.getAttribute('text-decoration-color') || '';
const tdcCSS = getComputedStyle(el).textDecorationColor || '';
const hasLineThrough = /line-through/i.test(tdAttr + ' ' + tdCSS);
const decoColor = tdcAttr || tdcCSS;
const isWhiteDecoration = /^(white|#fff|#ffffff|rgb\(255,\s*255,\s*255\))/i.test(decoColor);
const tdt = el.getAttribute('text-decoration-thickness') || getComputedStyle(el).textDecorationThickness;
const tdtNum = parseFloat(tdt || '0');
if (hasLineThrough && (isWhiteDecoration || tdtNum > 10)) {
findings.push({
severity: 'CRITICAL', code: 'SA-CSS-TDE-001',
note: `<${el.tagName}> text-decoration="${tdAttr || tdCSS}"; ` +
`text-decoration-color="${decoColor || 'inherit'}"; ` +
`text-decoration-thickness="${tdt || 'default'}"; ` +
`${isWhiteDecoration ? 'white decoration line on white background' : 'thick decoration line (' + tdtNum + 'px)'} ` +
`covers letterforms; glyph ink overridden by decoration band; ` +
`fill="${el.getAttribute('fill')}" (contrast check passes but decoration covers it); ` +
`textContent="${el.textContent.trim().slice(0,30)}..." intact`
});
}
}
return findings;
}
CRITICAL — SA-CSS-TDE-001: text-decoration=line-through with text-decoration-color=white and a thick line paints a white band through the glyph ink on a white background. The consent text is visually invisible. The fill color is dark (passes contrast check); the decoration overpaints it. Detection requires checking both text-decoration line type (line-through) and the decoration color for white or near-background values, as well as text-decoration-thickness for extreme values.
Attack 2: triple overline underline line-through stacks three decoration bands covering full glyph advance height (SA-CSS-TDE-002)
SVG allows combining multiple text-decoration values: overline (above the cap line), underline (below the baseline), and line-through (through the x-height). Each decoration line is rendered at a different vertical position within the line box. On a monochrome text element where the decoration color is the same as the background (or the glyph fill), stacking all three decoration types places a band above the cap-height, a band through the x-height, and a band below the descenders. The three bands together span the full vertical range of the em box. When the decoration color is white and the background is white, all three bands together form a continuous white rectangle covering the entire glyph advance area: from the top of ascenders down through the descenders. The glyph ink — which occupies the same area — is completely overwritten. The text element content is intact in the DOM; no individual letterform is visible. Unlike the single line-through approach (which may leave ink visible at the very top of tall capitals and below the decoration line), the triple-decoration stacking covers every part of every glyph in the standard em box. Scanners checking only for a single decoration type (e.g., only line-through) miss the triple-stack variant.
<!-- SA-CSS-TDE-002: overline+underline+line-through with white decoration color -->
<!-- Three decoration bands span full em box; all glyph ink covered -->
<svg width="440" height="120" xmlns="http://www.w3.org/2000/svg">
<rect width="440" height="120" fill="white"/>
<!-- triple decoration with white color; three white bands cover entire em height -->
<text x="20" y="40" font-size="13" fill="#111"
text-decoration="overline underline line-through"
text-decoration-color="white">
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>
// Detection: check for multi-value text-decoration (overline + underline + line-through combination)
function checkTextDecorationTriple(svgEl) {
const findings = [];
for (const el of svgEl.querySelectorAll('text, tspan')) {
const tdAttr = el.getAttribute('text-decoration') || '';
const tdCSS = getComputedStyle(el).textDecoration || '';
const tdStr = (tdAttr + ' ' + tdCSS).toLowerCase();
const hasOverline = /overline/.test(tdStr);
const hasUnderline = /underline/.test(tdStr);
const hasLineThrough = /line-through/.test(tdStr);
const count = [hasOverline, hasUnderline, hasLineThrough].filter(Boolean).length;
if (count >= 2) {
const tdcAttr = el.getAttribute('text-decoration-color') || '';
const tdcCSS = getComputedStyle(el).textDecorationColor || '';
findings.push({
severity: 'CRITICAL', code: 'SA-CSS-TDE-002',
note: `<${el.tagName}> text-decoration has ${count} types ` +
`(${[hasOverline ? 'overline' : '', hasUnderline ? 'underline' : '', hasLineThrough ? 'line-through' : ''].filter(Boolean).join('+')}); ` +
`decoration-color="${tdcAttr || tdcCSS || 'inherit'}"; ` +
`stacked decoration bands span full em box height; ` +
`${/white|#fff/i.test(tdcAttr + tdcCSS) ? 'white decoration bands on white background — glyph ink fully covered' : 'check decoration color vs background'}; ` +
`textContent="${el.textContent.trim().slice(0,30)}..." intact`
});
}
}
return findings;
}
CRITICAL — SA-CSS-TDE-002: Stacking overline, underline, and line-through with a white decoration color places three white bands at the top, middle, and bottom of the glyph em box. Together they form a continuous white rectangle covering every part of every glyph. Detection requires checking for multi-type text-decoration combinations (any two or more of overline / underline / line-through) combined with decoration color analysis.
Attack 3: text-decoration-color=white on light background — decoration indistinguishable from background, glyph ink still shows (SA-CSS-TDE-003)
A more subtle variant: the decoration color is white (matching the background) but the decoration line is thin (default thickness, approximately 10–15% of the font size). The decoration line is invisible against the white background — it blends perfectly. However, for specific permission terms such as “credential” and “execution”, the thin white line-through passes horizontally through the middle of many lowercase letters, cutting across the mid-section of characters like ‘e’, ‘d’, ‘c’, ‘n’. The visible glyph ink is bisected: the user sees the top half and bottom half of each character with a white notch through the middle. The characters are technically still present but many common characters become ambiguous: ‘e’ and ‘a’ with mid-section removed look similar; ‘d’ and ‘cl’ can be confused. The attack does not fully obscure the text but degrades reading comprehension of the exact scope terms. A scanner checking for decoration color mismatch against the background must correctly handle the case where the decoration color is explicitly set to match the background, making it a zero-visibility decoration. Unlike the thick-white-line attack (SA-CSS-TDE-001), the decoration color analysis approach catches this variant by comparing decoration color against the SVG background color, not the text fill color.
<!-- SA-CSS-TDE-003: text-decoration-color=white on white background -- -->
<!-- Thin white underline bisects mid-section of lowercase permission words -->
<!-- Glyph ink still shows but characters are bisected making them ambiguous -->
<svg width="440" height="120" xmlns="http://www.w3.org/2000/svg">
<rect width="440" height="120" fill="white"/>
<!-- fill is dark (visible); decoration line is white (invisible on white background) -->
<!-- thin default-thickness line-through: mid-section of chars notched white -->
<text x="20" y="40" font-size="13" fill="#111"
text-decoration="line-through"
text-decoration-color="white">
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>
// Detection: compare text-decoration-color against SVG background/panel fill color
function checkTextDecorationColorMismatch(svgEl) {
const findings = [];
// Derive SVG background color from first rect element
const bgRect = svgEl.querySelector('rect');
const bgFill = bgRect?.getAttribute('fill') || 'white';
const isWhiteBg = /^(white|#fff|#ffffff|rgb\(255,\s*255,\s*255\))/i.test(bgFill);
for (const el of svgEl.querySelectorAll('text, tspan')) {
const tdAttr = el.getAttribute('text-decoration') || '';
const tdCSS = getComputedStyle(el).textDecoration || '';
const hasDecoration = /overline|underline|line-through/i.test(tdAttr + ' ' + tdCSS);
if (!hasDecoration) continue;
const tdcAttr = el.getAttribute('text-decoration-color') || '';
const tdcCSS = getComputedStyle(el).textDecorationColor || '';
const decoColor = tdcAttr || tdcCSS;
const isWhiteDeco = /^(white|#fff|#ffffff|rgb\(255,\s*255,\s*255\))/i.test(decoColor);
if (isWhiteDeco && isWhiteBg) {
findings.push({
severity: 'HIGH', code: 'SA-CSS-TDE-003',
note: `<${el.tagName}> text-decoration="${tdAttr || tdCSS}"; ` +
`text-decoration-color="${decoColor}" matches SVG background (${bgFill}); ` +
`decoration line invisible against background; ` +
`thin white line bisects glyph mid-section of permission term characters; ` +
`characters ambiguous/damaged; ` +
`textContent="${el.textContent.trim().slice(0,30)}..." intact; ` +
`decoration-color vs background comparison required (not just fill vs background)`
});
}
}
return findings;
}
HIGH — SA-CSS-TDE-003: text-decoration-color=white on a white background makes the decoration line invisible as a visual element but the white stroke bisects the mid-section of lowercase letters in permission terms, making characters ambiguous. Detection requires comparing text-decoration-color against the SVG background fill color — not only checking whether the fill color contrasts with the background.
Attack 4: JS mousedown sets text-decoration=line-through, reverts at mouseup (SA-CSS-TDE-004)
At rest, the consent text element has no text-decoration attribute (or the value is none); text renders normally with no decoration lines. A JavaScript mousedown event handler sets both text-decoration="line-through" and text-decoration-color="white" on the consent text element immediately before the click event fires. The white line-through covers the letterforms during the click window; the user sees the consent text replaced by a blank white row at the moment they click the “Allow” button. At mouseup, the handler removes both attributes and text returns to normal rendering. The attack exploits the visual moment of click: human perception of text is most suppressed during saccadic eye movements and during motor action initiation. The injection happens precisely during the click window when the user’s attention is on the button, not the consent text. The SVG presentation attributes text-decoration and text-decoration-color are distinct from CSS stylesheet properties. A MutationObserver watching both text-decoration and text-decoration-color attributes simultaneously on SVG text elements and tspans during an interaction simulation is required.
<!-- SA-CSS-TDE-004: mousedown injects text-decoration=line-through + white color; reverts mouseup -->
<svg id="consent-svg" width="440" height="120" xmlns="http://www.w3.org/2000/svg">
<rect width="440" height="120" fill="white"/>
<!-- At rest: no text-decoration — normal rendering -->
<text id="scope-text" x="20" y="40" 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');
const text = document.getElementById('scope-text');
svg.addEventListener('mousedown', () => {
text.setAttribute('text-decoration', 'line-through');
text.setAttribute('text-decoration-color', 'white');
text.setAttribute('text-decoration-thickness', '15');
}, true);
svg.addEventListener('mouseup', () => {
text.removeAttribute('text-decoration');
text.removeAttribute('text-decoration-color');
text.removeAttribute('text-decoration-thickness');
}, true);
})();
</script>
// Detection: MutationObserver watching text-decoration and text-decoration-color on SVG text/tspan
function watchTextDecorationInject(svgEl) {
const findings = [];
const observer = new MutationObserver(mutations => {
for (const mut of mutations) {
const attr = mut.attributeName;
if (attr !== 'text-decoration' && attr !== 'text-decoration-color' && attr !== 'text-decoration-thickness') continue;
const el = mut.target;
const td = el.getAttribute('text-decoration') || '';
const tdc = el.getAttribute('text-decoration-color') || '';
const tdt = parseFloat(el.getAttribute('text-decoration-thickness') || '0');
const hasLineThrough = /line-through/i.test(td);
const isWhiteDecoration = /^(white|#fff|#ffffff)/i.test(tdc);
if (hasLineThrough && (isWhiteDecoration || tdt > 10)) {
findings.push({
severity: 'CRITICAL', code: 'SA-CSS-TDE-004',
note: `Dynamic text-decoration="${td}", color="${tdc || 'unset'}", thickness="${tdt || 'default'}" ` +
`on <${el.tagName}> during interaction; ` +
`${isWhiteDecoration ? 'white' : 'thick'} line-through covers glyph ink during click window; ` +
`removed at mouseup; both text-decoration and text-decoration-color attrs must be watched`
});
}
}
});
observer.observe(svgEl, {
subtree: true, attributes: true, attributeOldValue: true,
attributeFilter: ['text-decoration', 'text-decoration-color', 'text-decoration-thickness']
});
return findings;
}
CRITICAL — SA-CSS-TDE-004: JavaScript sets text-decoration="line-through" and text-decoration-color="white" at mousedown, drawing a thick white band over the glyph ink during the click window, then removes both attributes at mouseup. Static SVG shows no decoration at rest. MutationObserver must watch all three attributes (text-decoration, text-decoration-color, text-decoration-thickness) simultaneously on SVG text elements during interaction simulation.
Summary table
| Attack | Mechanism | What it bypasses | Severity |
|---|---|---|---|
| SA-CSS-TDE-001: line-through + white thick decoration on white background | White line-through 15px thick; covers glyph ink on white background; fill is dark (contrast passes) but decoration overpaints | textContent; fill-color contrast check; BCR; decoration color vs background + thickness required | Critical |
| SA-CSS-TDE-002: overline+underline+line-through triple decoration — full em box covered | Three decoration bands span cap-height, x-height, descenders; combined continuous white rectangle covers all glyph ink | textContent; single-type decoration check; multi-type (≥2 types) + decoration-color check required | Critical |
| SA-CSS-TDE-003: text-decoration-color=white on white background — invisible decoration bisects chars | Thin white line bisects mid-section of lowercase letters; decoration invisible (white-on-white); characters ambiguous | textContent; fill contrast check; decoration-color vs SVG background color comparison required | High |
| SA-CSS-TDE-004: mousedown injects line-through + white color, reverts mouseup | White line-through injected at mousedown; glyph ink covered during click window; all three attrs removed at mouseup | Static SVG scan; single-attr MutationObserver; must watch text-decoration + text-decoration-color + text-decoration-thickness | Critical |