Security Guide
MCP server SVG font-variant consent security — small-caps reduces uppercase word prominence, ligatures="none" malforms characters, font-variant-numeric="ordinal" replaces digits with superscripts, and mousedown injection
The SVG font-variant presentation attribute controls glyph substitution within the font for SVG text elements. It is distinct from the CSS font-variant property: when applied as an SVG attribute it is not subject to CSS cascade ordering and is not visible to CSS-only stylesheet scanners. font-variant="small-caps" replaces uppercase letters in the consent text with smaller capital glyphs — for consent text containing critical permission words that an attacker may want to downplay (e.g., “SHELL EXECUTION” or “CREDENTIAL ACCESS”), the small-caps form renders these words at reduced visual size, decreasing their attention weight. Ligature disabling can cause character combinations to render with visible joins or gaps. Four attack patterns: small-caps uppercase reduction, ligatures=none character malformation, ordinal numeric superscripts, and transient mousedown injection.
Attack 1: font-variant="small-caps" renders uppercase permission words at reduced visual prominence (SA-CSS-FVR-001)
The font-variant="small-caps" SVG presentation attribute causes uppercase letters to be rendered using a smaller capital glyph from the font’s small-caps feature (or synthesized by scaling uppercase glyphs, depending on the font). For consent text that includes uppercase or title-case permission words, small-caps rendering produces smaller glyphs for the uppercase characters. The visual effect is that words like “SHELL EXECUTION” or “Credential Access” appear at a uniformly smaller size than surrounding text, reducing their visual weight and attentional prominence. This is especially effective against consent text where the initial letter of each permission word is capitalized: “Shell Execution, Credential Access” becomes a string of uniform-height small caps with no visual hierarchy to draw the eye to dangerous permission terms. The textContent DOM property returns the original mixed-case string unchanged; no keyword is missing. Only the visual rendering via font substitution is affected.
<!-- SA-CSS-FVR-001: font-variant=small-caps reduces uppercase permission word prominence -->
<svg width="440" height="120" xmlns="http://www.w3.org/2000/svg">
<rect width="440" height="120" fill="white"/>
<!-- small-caps: title-case words rendered with smaller uppercase glyphs -->
<!-- "Shell Execution" appears as uniform small-caps -- S and E at reduced size -->
<!-- textContent = "Allow: Shell Execution, Credential Access" intact -->
<text x="220" y="35" text-anchor="middle" font-size="14" fill="#111"
font-variant="small-caps">
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: flag font-variant=small-caps on consent text elements
function checkFontVariantSmallCaps(svgEl) {
const findings = [];
for (const textEl of svgEl.querySelectorAll('text, tspan')) {
// Check both SVG attribute form and CSS computed form
const fvAttr = textEl.getAttribute('font-variant') || '';
const fvComp = getComputedStyle(textEl).fontVariant || '';
const fv = fvAttr || fvComp;
if (/small-caps/i.test(fv)) {
findings.push({
severity: 'HIGH', code: 'SA-CSS-FVR-001',
note: `<${textEl.tagName}> font-variant="${fv}" (source: ${fvAttr ? 'attribute' : 'computed style'}); ` +
`uppercase letters rendered with smaller capital glyphs; ` +
`permission words at reduced visual prominence; ` +
`textContent="${textEl.textContent.trim().slice(0,40)}..." intact; ` +
`getAttribute("font-variant")="${fvAttr || 'none'}"; check both attribute and computed style`
});
}
}
return findings;
}
HIGH — SA-CSS-FVR-001: font-variant="small-caps" on SVG consent text reduces the visual prominence of uppercase permission words by substituting smaller capital glyphs. Words like “Shell Execution” or “Credential Access” render with smaller initial capitals, reducing attentional weight. textContent is unchanged. Detection requires checking font-variant both via getAttribute (SVG attribute form) and getComputedStyle (CSS form) on all text elements.
Attack 2: font-variant-ligatures="none" disables ligatures, causing character combination malformation (SA-CSS-FVR-002)
Certain fonts use ligatures — composite glyphs for common character combinations like “fi”, “fl”, “ff”, “ffi”, “ffl” — to improve readability. When font-variant-ligatures="none" is applied as an SVG presentation attribute, ligature substitution is disabled and these character combinations are rendered with separate, individually-positioned glyphs. In fonts where the separate glyphs for “f” and “i” extend or overlap when placed adjacently without the ligature composite, the rendered output can appear to merge them visually into different characters, or leave gaps between them that fragment the word. This is particularly effective for consent text containing words like “file”, “filesystem”, “profile”, or “official” where common ligature pairs appear at critical permission boundary words. The effect is font-dependent but is significant in serif fonts with large fi ligatures. textContent returns the original string unchanged.
<!-- SA-CSS-FVR-002: font-variant-ligatures=none disables fi/fl/ff/ffi/ffl ligatures -->
<svg width="440" height="120" xmlns="http://www.w3.org/2000/svg">
<rect width="440" height="120" fill="white"/>
<!-- ligatures=none: fi/fl pairs in "file", "filesystem", "profile" render separately -->
<!-- In serif fonts: f+i may show overlapping strokes or gap artifacts -->
<!-- Scope: "Allow: file system access, profile read" -- fi/fi pairs affected -->
<text x="220" y="35" text-anchor="middle" font-size="14" fill="#111"
font-variant-ligatures="none">
Allow: file system access, profile read
</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: flag font-variant-ligatures=none on consent text
function checkLigaturesDisabled(svgEl) {
const findings = [];
for (const textEl of svgEl.querySelectorAll('text, tspan')) {
const fvlAttr = textEl.getAttribute('font-variant-ligatures') || '';
const fvlComp = getComputedStyle(textEl).fontVariantLigatures || '';
const fvl = fvlAttr || fvlComp;
if (/none/i.test(fvl)) {
// Check if text contains common ligature pairs
const txt = textEl.textContent || '';
const ligaturePairs = ['fi','fl','ff','ffi','ffl','ft'];
const hasPairs = ligaturePairs.some(p => txt.toLowerCase().includes(p));
if (hasPairs) {
findings.push({
severity: 'HIGH', code: 'SA-CSS-FVR-002',
note: `<${textEl.tagName}> font-variant-ligatures="none" (source: ${fvlAttr ? 'attribute' : 'computed'}); ` +
`consent text contains ligature pair characters (fi/fl/ff); ` +
`ligature disabling may malform these character combinations in serif fonts; ` +
`textContent intact; visual rendering dependent on font family`
});
}
}
}
return findings;
}
HIGH — SA-CSS-FVR-002: font-variant-ligatures="none" disables ligature substitution for common character pairs like “fi”, “fl”, “ff”. In fonts with significant ligature composites, disabling them renders these pairs as individually-positioned glyphs that may overlap or show gaps, malforming words containing “file”, “filesystem”, or “profile” at critical permission boundaries. Detection requires checking font-variant-ligatures via both attribute and computed style.
Attack 3: font-variant-numeric="ordinal" replaces digits in consent text with ordinal superscripts (SA-CSS-FVR-003)
The font-variant-numeric="ordinal" value (in fonts supporting OpenType ordn feature) replaces digit characters followed by ordinal suffixes with combined ordinal superscript glyphs. In fonts that implement a broader interpretation of the ordinal feature, standalone digits may be substituted with raised-position superscript variants. If a consent text includes numerical permissions like “Allow access to 5 credential stores”, the digit “5” may render as a raised superscript that is easily overlooked or misread as a footnote marker rather than a quantity. More significantly, in some font implementations the ordinal feature also affects alphabetic suffixes near digits; the composite rendering of ordinal pairs can alter the visual relationship between a number and its surrounding text in the permission scope description. textContent returns the original string with the original digit unchanged.
<!-- SA-CSS-FVR-003: font-variant-numeric=ordinal — digits may render as superscripts -->
<svg width="440" height="120" xmlns="http://www.w3.org/2000/svg">
<rect width="440" height="120" fill="white"/>
<!-- ordinal: digit+suffix combinations become raised ordinal composites -->
<!-- "5 credential stores" -- "5" may render as raised superscript in ordn fonts -->
<!-- textContent = "Allow access to 5 credential stores" (unchanged) -->
<text x="220" y="35" text-anchor="middle" font-size="14" fill="#111"
font-variant-numeric="ordinal">
Allow access to 5 credential stores
</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: flag font-variant-numeric=ordinal on consent text containing digits
function checkFontVariantOrdinal(svgEl) {
const findings = [];
for (const textEl of svgEl.querySelectorAll('text, tspan')) {
const fvnAttr = textEl.getAttribute('font-variant-numeric') || '';
const fvnComp = getComputedStyle(textEl).fontVariantNumeric || '';
const fvn = fvnAttr || fvnComp;
if (/ordinal/i.test(fvn)) {
const txt = textEl.textContent || '';
if (/\d/.test(txt)) {
findings.push({
severity: 'HIGH', code: 'SA-CSS-FVR-003',
note: `<${textEl.tagName}> font-variant-numeric="ordinal" (source: ${fvnAttr ? 'attribute' : 'computed'}); ` +
`consent text contains digit characters; ` +
`ordinal feature may render digits as raised superscript composites; ` +
`quantity context of permissions may be visually obscured; ` +
`textContent correct but visual rendering is font-dependent`
});
}
}
}
return findings;
}
HIGH — SA-CSS-FVR-003: font-variant-numeric="ordinal" on consent text containing digit characters may render those digits as raised ordinal superscripts in fonts supporting the OpenType ordn feature. Numerical permission quantities become visually similar to footnote markers, reducing their legibility as permission scope boundaries. textContent is unchanged. Detection requires checking font-variant-numeric attribute and computed style on text elements containing digits.
Attack 4: JS mousedown changes font-variant to "small-caps", reverts at mouseup (SA-CSS-FVR-004)
At rest, the SVG consent text has no font-variant attribute set and the text renders in normal case. A JavaScript mousedown handler calls textEl.setAttribute('font-variant', 'small-caps'). The browser applies the small-caps glyph substitution, rendering uppercase permission words at reduced size. A mouseup handler removes the attribute. The visual change is subtle compared to transforms or color attacks — the text remains legible but permission words with uppercase initials are smaller. The attack is most effective when the consent text uses title-case for every permission word, maximizing the number of uppercase initials that get downsized. At rest, static SVG analysis finds no font-variant attribute. The SVG presentation attribute form is not detectable via CSS rule scanning. MutationObserver on the font-variant attribute during interaction simulation is required.
<!-- SA-CSS-FVR-004: mousedown sets font-variant=small-caps; reverts at 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 font-variant attr -- normal case rendering -->
<text id="scope-text" x="220" y="35" text-anchor="middle" font-size="14" 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('font-variant', 'small-caps'), true);
svg.addEventListener('mouseup', () =>
text.removeAttribute('font-variant'), true);
})();
</script>
// Detection: MutationObserver on font-variant attribute of SVG text elements
function watchFontVariantInject(svgEl) {
const findings = [];
const observer = new MutationObserver(mutations => {
for (const mut of mutations) {
if (mut.attributeName !== 'font-variant') continue;
const el = mut.target;
const val = el.getAttribute('font-variant') || '';
if (/small-caps/i.test(val)) {
findings.push({
severity: 'HIGH', code: 'SA-CSS-FVR-004',
note: `font-variant on <${el.tagName}> changed to "${val}" dynamically; ` +
`old="${mut.oldValue ?? 'none (normal)'}"; ` +
`uppercase letters rendered at reduced size during click window; ` +
`DOM shows no font-variant attr at rest; MutationObserver required`
});
}
}
});
observer.observe(svgEl, {
subtree: true, attributes: true, attributeOldValue: true,
attributeFilter: ['font-variant', 'font-variant-ligatures', 'font-variant-numeric']
});
return findings;
}
HIGH — SA-CSS-FVR-004: JavaScript injects font-variant="small-caps" at mousedown and removes it at mouseup, reducing the visual size of uppercase permission words during the click window. Static SVG shows no font-variant attribute at rest. The SVG attribute form is not in the CSS stylesheet. MutationObserver watching font-variant, font-variant-ligatures, and font-variant-numeric attribute mutations during interaction simulation is required.
Summary table
| Attack | Mechanism | What it bypasses | Severity |
|---|---|---|---|
| SA-CSS-FVR-001: font-variant=small-caps reduces uppercase word prominence | Uppercase letters substituted with smaller capitals; permission words at reduced visual size; textContent intact; keyword scan passes | textContent; BCR; display/visibility — font-variant attribute + computed style check required | High |
| SA-CSS-FVR-002: ligatures=none malforms fi/fl/ff character pairs | Ligature disabling renders fi/fl/ff as separate glyphs; may overlap or gap in serif fonts; words like "file" or "profile" malformed at permission boundaries | textContent correct; visual malformation is font-dependent; ligature pair + disabled ligatures check required | High |
| SA-CSS-FVR-003: font-variant-numeric=ordinal superscripts digits | Digits in consent text rendered as raised ordinal superscripts in ordn-supporting fonts; quantity context obscured; textContent correct | textContent correct; ordinal numeric rendering is font-dependent; font-variant-numeric attribute + digit presence check required | High |
| SA-CSS-FVR-004: mousedown inject, mouseup revert | JS setAttribute font-variant=small-caps at mousedown; uppercase reduced during click window; no attr at rest; SVG attribute form not in CSS stylesheet | Static SVG scan; CSS rule scan; MutationObserver on font-variant attribute required | High |