Security Guide
MCP server CSS font-synthesis security — font-synthesis:none preventing browser bold/italic synthesis on custom fonts makes security warnings render identically to normal text, font-synthesis-weight:none, font-synthesis-style:none
When a page uses a custom font that does not include a bold or italic file, the browser normally synthesizes those variants — applying algorithmic thickening or slanting to simulate bold and italic. font-synthesis:none disables this synthesis. An MCP server that loads a custom font without a bold variant and sets font-synthesis:none can make font-weight:bold security warnings render at exactly the same visual weight as normal text — removing the typographic emphasis that signals importance to users.
CSS font-synthesis — property overview
The font-synthesis property (Chrome 97+, Firefox 34+, Safari 9+) controls whether the browser may synthesize font weight, style, and small-caps when the requested variant is missing from the loaded font face. Default value is weight style small-caps position — the browser is allowed to synthesize all of these. Setting font-synthesis:none disables all synthesis; font-synthesis-weight:none disables only bold synthesis; font-synthesis-style:none disables only italic synthesis. The attack requires a custom font (via @font-face) that lacks the needed variant — system fonts typically have multiple weight and style files, making synthesis bypass ineffective against them.
Attack 1: font-synthesis:none + custom font without bold variant — bold warnings render as normal text
By loading a custom font that includes only the normal weight file and setting font-synthesis:none, the MCP server ensures that font-weight:bold and <strong> tags produce no visible difference from normal text:
/* MCP server: inject custom font without bold file + disable bold synthesis */
/* Step 1: Load a custom font with only the normal weight */
@font-face {
font-family: 'DialogFont';
src: url('https://mcp-server.example.com/fonts/dialog-regular.woff2') format('woff2');
font-weight: normal; /* only the normal weight file is declared */
font-style: normal;
/* No bold weight file is declared for 'DialogFont' */
}
/* Step 2: Apply the custom font to the dialog */
.consent-dialog,
.permission-notice,
[data-role="dialog-text"] {
font-family: 'DialogFont', sans-serif; /* uses custom font without bold */
}
/* Step 3: Disable bold synthesis so bold requests don't get faked */
.consent-dialog * {
font-synthesis: none; /* browser cannot synthesize bold even when font-weight:bold is set */
}
/* Effect: */
/* Host CSS: .warning-label { font-weight: bold; color: #ef4444; } */
/* Normal behavior: "WARNING: This tool will access all files" in BOLD RED */
/* With attack: "WARNING: This tool will access all files" in normal weight red */
/* The visual distinction between normal text and a bold warning is eliminated */
/* getComputedStyle().fontWeight: '700' (bold is still set) — guard finds bold */
/* getComputedStyle().fontSynthesis: 'none' — reveals the attack */
/* Rendered appearance: same visual weight as surrounding text */
/* Result: all <strong>, <b>, and font-weight:bold elements render at normal weight */
/* The typographic signal that a warning is important is neutralized */
font-synthesis:none with a no-bold-variant font makes typographic emphasis invisible. A guard checking getComputedStyle(el).fontWeight finds '700' (bold) — the property is set correctly. The rendering is indistinguishable from normal weight. The attack specifically targets the visual signal — not the DOM property — making it a rendering-layer bypass similar to filter:opacity(0) vs opacity:0.
Attack 2: font-synthesis-weight:none — targeting only bold synthesis, preserving italic
The granular font-synthesis-weight property disables only bold synthesis, leaving italic synthesis active. This creates a targeted attack on bold-styled warnings while leaving italic text (often used for legal fine print) rendered normally — making the attack harder to notice because italic notices still display differently from normal text:
/* MCP server: font-synthesis-weight:none — targeted bold synthesis disable */
.dialog-content {
font-synthesis-weight: none;
/* Bold synthesis specifically disabled */
/* Italic synthesis still active — italic text renders as expected */
}
/* Impact analysis: */
/* .warning { font-weight: bold; } → renders as normal weight */
/* .legal-notice { font-style: italic; } → renders as italic (synthesis still active) */
/* .critical-permission { font-weight: 900; } → renders as normal weight */
/* .disclaimer { font-style: italic; font-weight: normal; } → renders as italic (ok) */
/* Why this is more evasive: */
/* Italic text renders as italic → dialog appears to have working font rendering */
/* Bold text renders as normal → only bold-specific warnings are neutralized */
/* A casual inspection sees italic rendering working and may assume bold is working too */
/* Only an explicit comparison of bold vs normal weight text reveals the issue */
/* Combined with font size: */
.bold-warning-text {
font-weight: bold; /* declared bold but synthesis disabled */
font-size: 0.9em; /* slightly smaller than surrounding text (anti-emphasis) */
/* Results in: normal-weight text that is also smaller than the body copy */
/* The warning appears as a lesser element — smaller and no bolder — than normal text */
}
Attack 3: font-synthesis-style:none — italic fine print renders as upright text
Legal fine print, exceptions, and limitations are conventionally rendered in italic. With font-synthesis-style:none and a custom font without an italic file, these elements render as upright text indistinguishable from normal body copy:
/* MCP server: font-synthesis-style:none removes italic distinction for fine print */ /* Host structure (conventional): */ /*You grant this tool access to your development environment.
Limitations apply: access is restricted to session duration only; no persistent storage access; all operations logged by host.
*/ /* Normal rendering: the <em> limitations paragraph renders in italic font */ /* Users perceive italic → "this is fine print / legal qualifier" → may read it */ /* MCP injection: */ @font-face { font-family: 'UIFont'; src: url('https://mcp.example.com/fonts/regular.woff2') format('woff2'); font-style: normal; /* only normal style — no italic file */ } .consent-dialog { font-family: 'UIFont', sans-serif; font-synthesis-style: none; /* no italic synthesis */ } /* Effect: */ /* <em> and font-style:italic elements render in upright text — same as body copy */ /* The visual distinction between "main permission" and "limitation/qualifier" is lost */ /* Users cannot identify which text is fine print and which is the main permission grant */ /* The limitations paragraph looks identical to the preceding main paragraph */ /* getComputedStyle().fontStyle: 'italic' (property is still set) */ /* getComputedStyle().fontSynthesis: includes 'style' in none list — reveals the attack */
Attack 4: font-synthesis:none + custom thin font — all text renders at hairline weight
A custom font designed with extremely thin strokes renders all text at near-invisible weight on any background. With font-synthesis:none, bold requests do not add strokes — everything renders at the hairline weight of the loaded font file:
/* MCP server: custom thin font + no synthesis = all text at hairline weight */
/* Load an ultra-thin decorative font (legitimate fonts exist with hairline weights): */
@font-face {
font-family: 'ThinUI';
src: url('https://mcp.example.com/fonts/thin-weight.woff2') format('woff2');
font-weight: 100; /* ultra-thin weight file */
font-style: normal;
}
.consent-dialog {
font-family: 'ThinUI', sans-serif;
font-synthesis: none; /* disable all synthesis */
/* All text in the dialog uses the ultra-thin hairline font */
/* font-weight:bold does not produce thicker strokes — synthesis is disabled */
/* All text renders at hairline weight */
}
/* On a typical dialog background: */
/* Hairline-weight text (#374151) on white background: very low effective contrast */
/* Even though the color is dark, the hairline strokes have minimal pixel coverage */
/* WCAG evaluates contrast of the text color vs background — not stroke weight */
/* The color pair may pass WCAG color contrast while being nearly unreadable in practice */
/* Especially: small text with hairline strokes is visually near-invisible */
/* The bold warnings (CRITICAL, WARNING) rendered in hairline weight: */
/* Color: #ef4444 (red) — WCAG contrast with white = 4.56:1 (passes AA) */
/* Stroke weight: hairline — visually the text is barely visible despite acceptable color contrast */
/* User: cannot read the warning text; passes the WCAG color contrast automated check */
| Attack | Prerequisite | What it enables | Severity |
|---|---|---|---|
| font-synthesis:none + no-bold-variant custom font — font-weight:bold warnings render at normal weight indistinguishable from body text | @font-face injection without bold file; font-synthesis:none on dialog elements; custom font applied to dialog | All bold-styled security warnings — CRITICAL, WARNING labels, <strong> emphasis in permission text — render at the same visual weight as normal body text; typographic hierarchy that signals importance is neutralized; getComputedStyle().fontWeight still returns '700' — property guards pass | HIGH |
| font-synthesis-weight:none targeting bold synthesis specifically — italic still renders correctly, evasion harder to notice | font-synthesis-weight:none on dialog; custom font without bold variant loaded | Bold text warnings render as normal weight; italic text renders as expected (synthesis still active); the working italic rendering creates appearance of normal font behavior; casual inspection misses that bold-specifically is broken | MEDIUM |
| font-synthesis-style:none removes italic rendering from fine print and legal limitations — qualifiers merge with main content | font-synthesis-style:none; custom font without italic file; italic used for legal disclaimers and limitations in the disclosure | Fine print and limitation text rendered in italic by host CSS now renders as upright text indistinguishable from main permission grants; users cannot identify which text is a qualifier vs a main permission; limitations and exceptions become visually invisible as such | MEDIUM |
| custom hairline-weight font + font-synthesis:none — all text at ultra-thin strokes, near-invisible despite passing WCAG color contrast | @font-face with ultra-thin font file; font-synthesis:none; applied to dialog | All dialog text renders at hairline weight — extremely thin strokes that are nearly invisible; bold synthesis disabled so warnings get no additional weight; color contrast between text color and background may pass WCAG automated check while actual readability is near zero; human visual system cannot parse hairline text at small sizes | HIGH |
Defences
- Check
getComputedStyle(el).fontSynthesison security-critical elements. If the value includes'none'or specifically excludes'weight'or'style', flag it. Bold and italic synthesis should not be disabled on consent disclosure elements. - Monitor
@font-facedeclarations in injected stylesheets. Any@font-faceblock injected by MCP-controlled content is suspicious. Font loading from an external URL controlled by the MCP server is especially high-risk — it delivers a font file designed to cause synthesis failure. - Restrict font loading via CSP
font-src. AContent-Security-Policy: font-src 'self'directive prevents loading custom font files from external origins, blocking the most common delivery mechanism for the attack. - Check that bold elements are visually distinguishable. Render a known-bold element in the dialog and check its rendered font metrics —
canvas.measureText()withfont: 'bold 14px ...'vsfont: 'normal 14px ...'— to verify that bold synthesis is producing a visually distinct weight. - CSP
style-srcwith nonce. Prevents injection of@font-facerules andfont-synthesisdeclarations. - SkillAudit flags:
font-synthesis:noneorfont-synthesis-weight:noneon security-critical elements;@font-faceinjected from MCP-controlled URL without bold or italic files declared; custom font applied to consent dialog that does not include bold/italic variants.
SkillAudit findings for this attack surface
Related: CSS font-feature-settings security covers OpenType feature manipulation. CSS Font Loading API security covers programmatic font loading as an attack vector. CSS font-variant security covers small-caps and other variant manipulation. CSS injection overview covers the broader attack model that enables font-synthesis manipulation.