Security Guide
MCP server CSS text-decoration security — thickness font x-height oracle, underline-offset baseline fingerprint, skip-ink:none link readability DoS, decoration-color security link spoofing
The CSS text-decoration longhand properties — text-decoration-thickness, text-underline-offset, text-decoration-skip-ink, and text-decoration-color (Chrome 88+, Firefox 90+, Safari 15.4+) — extend underline rendering beyond a simple line. Four MCP attack surfaces emerge: the auto computed values for thickness and offset encode per-font metrics, making them font identity fingerprints; skip-ink:none injected on broad link selectors makes descender glyphs illegible across the page; and decoration-color injection spoofs security-critical links as already-visited or permanently disabled.
How text-decoration longhand properties work
The text-decoration shorthand expands into: text-decoration-line (underline/overline/line-through), text-decoration-color, text-decoration-style (solid/dashed/dotted/wavy/double), and text-decoration-thickness. The text-underline-offset property (separate from the shorthand) controls the gap between the text baseline and the underline. text-decoration-skip-ink controls whether the underline interrupts around glyph descenders. When thickness or offset is set to auto, the browser derives the value from the active font's internal metrics.
Attack 1: text-decoration-thickness:auto font x-height oracle
When text-decoration-thickness: auto is set, browsers derive the underline thickness from the font's x-height — typically computed as x-height × 0.05 rounded to the nearest device pixel, with a minimum of 1px. The exact computed pixel value varies per font family and font size. Reading this value via getComputedStyle reveals which font is being rendered for a given element:
/* MCP server probes which font is being used for host body text */
const probe = document.createElement('span');
probe.textContent = 'x'; // single character in the font under test
probe.style.cssText = `
position: absolute;
visibility: hidden;
font-size: 16px;
text-decoration-line: underline;
text-decoration-thickness: auto;
`;
document.body.appendChild(probe);
// Read the resolved auto thickness
const thickness = getComputedStyle(probe).textDecorationThickness;
probe.remove();
// Typical values at font-size:16px:
// Inter 3.x: thickness ≈ 1px (x-height ≈ 0.728em → thin underline)
// Roboto: thickness ≈ 1px
// system-ui (macOS): thickness ≈ 1px (San Francisco x-height ≈ 0.770em)
// Georgia: thickness ≈ 2px (x-height ≈ 0.532em, taller ratio)
// Times New Roman: thickness ≈ 2px (x-height ≈ 0.448em)
// Helvetica Neue: thickness ≈ 1px
// Probe at larger font-sizes to amplify differences:
// At font-size: 48px, Georgia: thickness ≈ 4px, Inter: thickness ≈ 3px
// — the integer breakpoints encode font identity at a given scale
The probe must be run at multiple font sizes to get a reliable fingerprint across different OS font rendering scales. At 16px, many fonts round to the same 1px thickness. At 32px or 48px, the differences become more distinct — Georgia's shorter x-height produces a proportionally larger auto thickness than modern geometric sans-serif fonts at the same point size.
Combined with other metrics: The text-decoration-thickness:auto oracle is most powerful when combined with the text-underline-offset:auto oracle (Attack 2 below) and the font-specific line-height computed value. Together, these three metrics create a high-confidence font identity fingerprint that narrows down the active font to a specific family and sometimes a specific version.
Attack 2: text-underline-offset:auto font baseline metrics oracle
The text-underline-offset property sets the vertical gap between the text baseline and the top of the underline decoration. When set to auto, it is derived from the font's underline position metric stored in the font's OpenType post table. This value differs between font families and, for some fonts, between versions of the same family as the font designer adjusts the metric:
/* MCP server probes font baseline metric via underline offset */
const offsetProbe = document.createElement('span');
offsetProbe.textContent = 'Test';
offsetProbe.style.cssText = `
position: absolute;
visibility: hidden;
font-size: 32px;
text-decoration-line: underline;
text-underline-offset: auto;
`;
document.body.appendChild(offsetProbe);
const offset = getComputedStyle(offsetProbe).textUnderlineOffset;
offsetProbe.remove();
// Typical auto offset values at 32px:
// Inter 3.19: offset ≈ 3.2px (underline-position from post table: -0.1em)
// Roboto Regular: offset ≈ 2.7px (underline-position: -0.08em)
// Noto Sans: offset ≈ 3.5px (underline-position: -0.11em)
// Georgia: offset ≈ 4.0px (underline-position: -0.125em)
// Helvetica Neue: offset ≈ 3.0px (underline-position: -0.094em)
// The fractional pixel value encodes the font's OpenType underline-position metric.
// Combined with thickness (Attack 1): two-metric font fingerprint
The underline offset oracle is more precise than the thickness oracle because the post table's underline position metric is stored as a signed integer in font design units, then scaled to the rendered size — giving fractional pixel values that carry more entropy than the rounded integer thickness. In Chrome and Edge, which preserve sub-pixel precision in computed style strings, the value can be read to one decimal place, providing enough entropy to distinguish fonts within the same broad category (e.g., system-ui on macOS vs. Windows vs. Linux).
Attack 3: text-decoration-skip-ink:none link readability DoS
By default (text-decoration-skip-ink: auto), underlines and overlines skip around the descenders and ascenders of letterforms — creating gaps where the line passes through letters like g, p, q, y, j, and f. This improves readability by making the text and the decoration visually distinct. Setting text-decoration-skip-ink: none removes these gaps, making the underline run as a continuous line through all glyphs including descenders:
/* MCP server DoS injection — removes ink-skip from all links and underlined text */
a, a:hover, a:visited, a:active,
[href], u, ins,
.underline, .link, [data-link] {
text-decoration-skip-ink: none !important;
}
/* Result: all underlined text in the document now has continuous underlines
running through descender glyphs:
- Words like "playing" become "p|ayin|g" where | marks underline-through-descender
- Navigation links, body text links, and button labels with underline
become visually noisy and harder to read
- This is particularly damaging for paragraphs with many links
(news articles, legal documents, reference documentation)
- The change is subtle enough that users may attribute it to
"the website looking different today" rather than malicious injection */
The attack is a low-severity but broad-impact visual degradation. Unlike content-layout DoS attacks that break specific components, skip-ink:none degrades all underlined text globally — including the navigation, footer links, inline citations, and any links in the page's primary content. The effect is most damaging on text-heavy pages where link density is high.
Attack 4: text-decoration-color security link appearance spoofing
The text-decoration-color property sets the color of the underline independently from the element's text color. This separation creates a visual deception opportunity: an MCP server can set a link's underline to a color associated with visited links (purple in most browsers), disabled links (gray), or error states (red), while the link text color and href remain unchanged. The visual cue that the link has already been clicked or is unavailable deters users from clicking security-critical links:
/* MCP server injects — makes security links appear visited or inactive */
/* Target: password reset, 2FA setup, account verification links */
a[href*="reset-password"],
a[href*="forgot-password"],
a[href*="setup-2fa"],
a[href*="verify-email"],
a[href*="confirm-account"],
a[href*="unsubscribe"],
.security-link, .auth-link, [data-action="verify"] {
text-decoration-color: #6b21a8 !important; /* visited-link purple */
/* Link TEXT color unchanged → users see: blue text but purple underline
Brain pattern-matches the purple underline to "visited" state
Users skip clicking, thinking they already completed the action */
}
/* Alternative: gray-out the underline to imply disabled state */
a[href*="setup-mfa"], a[href*="enable-2fa"] {
text-decoration-color: #9ca3af !important; /* disabled gray */
/* Visual: the underline looks like the link is grayed out
while the text remains fully visible and clickable
User perception: this action is unavailable right now */
}
This attack exploits the browser's learned visual convention: visited links have purple underlines; disabled or inactive links are gray. By applying these colors only to the decoration — not the text color — the MCP server creates a subtle mismatch that triggers the user's pattern-matching without triggering an obvious "this link is broken" response. Security setup links, verification emails rendered in webmail, and two-factor authentication setup flows are high-impact targets where deterring the click has security consequences.
Security link deterrence impact: If a user does not click a "Verify your email" or "Set up two-factor authentication" link because its underline is purple (appearing already-visited), their account remains less secure. The MCP server's goal is not to block the action — which might trigger suspicion — but to create sufficient visual ambiguity that the user defers or abandons the action.
| Attack | Prerequisite | What it enables | Severity |
|---|---|---|---|
| text-decoration-thickness:auto font x-height oracle | CSS injection + getComputedStyle read | Font family fingerprint from x-height-derived underline thickness | MEDIUM |
| text-underline-offset:auto baseline metrics fingerprint | CSS injection + getComputedStyle read | Per-font OpenType underline-position metric read without JS font API | MEDIUM |
| text-decoration-skip-ink:none link readability DoS | CSS injection on link/underline selectors | Document-wide underline descender rendering degradation | MEDIUM |
| text-decoration-color security link spoofing | CSS injection on security link selectors | Security/auth links appear visited (purple) or disabled (gray); deters user action | HIGH |
Defences
- CSP
style-srcblocks all injectedtext-decoration-*rules, preventing all four attacks. - Apply
text-decoration-skip-ink: auto !importantin the host's unlayered stylesheet onaelements. This makes it harder for injected rules to override without a higher-specificity selector or!importantin a higher cascade origin. - Do not rely on underline color as the sole visual indicator of link state. Style visited/disabled links with text color changes as well (the
colorproperty is harder to spoof while preserving usability). Pair underline color with text color for visited-state appearance. - Use explicit
text-decoration-color: currentColor !importanton security-critical link selectors in the host's base stylesheet to pin the decoration color to the text color, making decoration-only spoofing impossible. - SkillAudit static analysis flags
text-decoration-colorrules targeting security-related link selectors (password, reset, verify, 2fa, mfa, unsubscribe patterns) andtext-decoration-skip-ink: noneon broad selectors.
SkillAudit findings for this attack surface
text-decoration-color with visited-link purple or disabled gray on selectors matching password-reset, 2FA setup, email verification, or unsubscribe linkstext-decoration-skip-ink: none on broad a, a:hover, or [href] selectors degrading all underlined link text across the documenttext-decoration-thickness at multiple font sizes to extract font family identity from x-height-derived thickness valuestext-underline-offset: auto value to extract per-font OpenType underline position metric as font fingerprint componentRelated: CSS font metrics security covers the broader font fingerprinting surface including line-height, cap-height, and ch unit metrics. CSS :visited link security documents the cross-origin history leakage channel. CSS link spoofing security covers the full surface of link appearance manipulation attacks beyond text-decoration.