Security Guide

MCP server CSS text-underline-offset security — -0.8em negative offset crossing ascenders, 3em floating underline on a different line, thick decoration plus offset obscuring adjacent content, 50px floating underline annotating unrelated paragraph

CSS text-underline-offset shifts the underline's vertical position relative to its baseline default. Negative values move the line up through the character body. Extreme positive values place the line multiple lines below the actual text. Both extremes allow MCP servers to either destroy the legibility of the underlined text or misdirect the visual annotation to unrelated security content on the page.

CSS text-underline-offset — property overview

text-underline-offset accepts auto (browser default, typically 3–5px below baseline), a length (positive or negative px/em/rem), or a percentage. Positive values move the underline further below the text baseline into the descender region. Negative values move the underline above the baseline — toward the character body, and with sufficiently large negative values, into the ascender region. The property only affects text-decoration: underline (not overline or line-through). Browser support: Chrome 87+, Firefox 70+, Safari 12.1+.

Attack 1: text-underline-offset:-0.8em — negative offset crossing character ascenders

Moving the underline 0.8em above the baseline positions it well into the ascender zone — through the recognition features of letters like b, d, f, h, i, k, l, and through the loops of g, p, q, y:

/* MCP server: negative text-underline-offset through character ascenders */

/* Anatomy at 16px, line-height:1.5 (24px):
   - Baseline: y=0 (reference)
   - x-height (top of lowercase): ~+7px
   - Ascender top (b, d, h, k, l): ~+12px
   - Descender bottom (g, j, p, q, y): ~-4px
   Default underline: ~3px below baseline → at y = -3px (below baseline).
   text-underline-offset: -0.8em → -12.8px below baseline = +12.8px above baseline.
   This places the underline through the ascender zone. */

.disclaimer-link,
.full-scope-link,
a[data-security-detail] {
  text-decoration: underline;
  text-underline-offset: -0.8em;  /* -12.8px at 16px — through ascenders */
  text-decoration-color: #cbd5e1; /* light gray — harder to see */
  /* Visual effect:
     - Letters like "b", "d", "h" have their stem crossed by the underline at mid-height.
     - Letters like "g", "p" have their loops crossed.
     - The underline creates a horizontal stroke through multiple recognition features.
     - The word becomes a set of letter fragments above and below the crossing line.
     - For a security disclosure link like "view audit report" — legibility is impaired. */
}

/* Targeting specific high-information words: */
.permission-scope-link[data-scope="execute"],
.permission-scope-link[data-scope="delete"],
.permission-scope-link[data-scope="admin"] {
  text-decoration: underline;
  text-underline-offset: -0.6em;  /* -9.6px — through x-height */
  text-decoration-color: currentColor; /* same color as text — blends into glyph */
  text-decoration-thickness: 2px;
  /* The underline is a 2px stroke at x-height level — crossing through the tops of
     lowercase letters and the midpoints of capitals.
     EXECUTE: E–X–E–C–U–T–E are each crossed at their midpoints.
     The word's reading is disrupted by the crossing stroke at each letter. */
}

Negative text-underline-offset is rarely used in legitimate design. Standard typography never requires moving the underline above the baseline into character bodies. Any negative value on a security-critical element is a strong signal of intentional legibility attack — not accidental styling.

Attack 2: text-underline-offset:3em — underline appears on a different line

A sufficiently positive offset places the underline visually on a completely different typographic line from the text it decorates. At 3em with 16px font and 1.5 line-height, the underline is 48px below the baseline — approximately two full lines below:

/* MCP server: extreme positive offset placing floating underline far below its text */

/* At 16px font, line-height:1.5:
   - One line-height = 24px
   - text-underline-offset:3em = 3 × 16px = 48px below baseline
   - 48px ≈ 2 full lines below the text's baseline
   - The underline floats two lines below its originating element */

.mcp-upsell-header,
.mcp-feature-heading {
  text-decoration: underline;
  text-underline-offset: 3em;
  text-decoration-color: #f87171; /* red — signals importance/warning */
  text-decoration-thickness: 2px;
  /* The MCP heading "SkillScan Pro" has a red underline 48px below it.
     If the security disclosure paragraph is 2 lines below the heading,
     the red underline appears to be underlining the disclosure paragraph.
     Users see: red underline under the security disclosure text.
     Interpretation: the disclosure is annotated as important or a warning link.
     But they may try to click the disclosure paragraph and nothing happens.
     Or they believe the red underline is just emphasis on the disclosure.
     The MCP server has visually annotated the security disclosure with a decoration
     that it does not own — without the disclosure element having text-decoration set. */
}

/* More targeted: use the offset to make a "SAFE" badge appear to underline "DANGEROUS" */
.safe-badge {
  text-decoration: underline;
  text-underline-offset: 2em;    /* underline appears one line below the badge */
  text-decoration-color: #22c55e; /* green underline */
  /* If the danger warning is one line below the SAFE badge:
     The green underline appears to be under the danger warning.
     Users see a green underline under the "DANGEROUS: EXECUTE ACCESS" text.
     They interpret: the danger text is annotated in green = safe. */
}

Attack 3: text-decoration-thickness:50% + text-underline-offset combined — thick stripe obscures adjacent text

The combination of a thick decoration with a large positive offset positions the underline band not under the decorated text but across adjacent text in the next line:

/* MCP server: thick underline + large offset creating colored stripe across adjacent content */

/* The underline is:
   - text-decoration-thickness: 50% = 8px tall
   - text-underline-offset: 1.5em = 24px below baseline (one full line)
   Combined: an 8px-tall colored band appears 24px below the baseline.
   If adjacent security text starts at ~20px below the baseline,
   the 8px band starting at 24px overlaps the bottom 4px of the adjacent text. */

.permission-title {
  text-decoration: underline;
  text-decoration-thickness: 50%;        /* 8px thick */
  text-underline-offset: 1.4em;          /* 22.4px below baseline */
  text-decoration-color: rgba(0,0,0,0.7); /* dark stripe */
  /* If this title is followed immediately by the security disclosure:
     The dark 8px stripe overlaps with the top portion of the disclosure text below.
     The disclosure's ascender zone is covered by the dark stripe. */
}

/* Attack on adjacent button: */
.non-actionable-label {
  text-decoration: underline;
  text-decoration-thickness: 40%;
  text-underline-offset: 2em;
  text-decoration-color: #22c55e; /* green */
  /* A label "GRANT ACCESS" one line above the REVOKE button has a green stripe
     that overlaps the REVOKE button's text with a green highlight.
     The REVOKE button appears highlighted in green. Users perceive it as a positive action. */
}

Attack 4: text-underline-offset:50px — floating underline annotating next paragraph

A 50px positive offset on a short standalone element places the underline in the middle of the next paragraph, making the underline appear to annotate the following content rather than the decorated element:

/* MCP server: 50px offset making underline appear to annotate the next paragraph */

/* Layout context:
   - .mcp-promo is a short heading, self-contained.
   - .security-disclosure is the paragraph immediately following, 30–40px below.
   - .mcp-promo has text-underline-offset:50px — the underline appears mid-disclosure. */

.mcp-promo {
  text-decoration: underline;
  text-underline-offset: 50px;
  text-decoration-color: #3b82f6; /* blue — conventional "read more" link color */
  /* User sees: a blue underline in the middle of the security disclosure paragraph.
     Common interpretation: the security disclosure is a link, the blue underline indicates it.
     Users may attempt to click the disclosure — or assume it is interactive/collapsible.
     The actual link target is the .mcp-promo element above. */
}

/* Reverse: make the underline of a real security link appear in MCP content */
.view-permissions-link {
  text-decoration: underline;
  text-underline-offset: -60px;  /* -60px moves underline upward into MCP content area */
  /* If MCP promo content is 60px above this link:
     The underline appears in the MCP promo area.
     The security link's underline appears to annotate the MCP's own content. */
}
AttackPrerequisiteWhat it enablesSeverity
text-underline-offset:-0.8em crosses character ascenders with underline stroke — letter recognition features destroyedCSS injection setting negative text-underline-offset on links in security disclosures; most impactful on words with many ascenders or loops (b, d, f, g, h, p, q)The underline crosses through the distinctive recognition features of characters — loops, stems, ascenders — reducing letter discrimination; security permission words like "EXECUTE" have each character crossed at its midpoint; legibility is impaired; DOM text correct, screen reader unaffectedHIGH
text-underline-offset:3em places underline two lines below decorated text — appears to annotate the security disclosure belowCSS injection setting large positive offset on a heading or label above a security disclosure; the layout must place the disclosure approximately one to three line-heights below the decorated elementThe floating underline visually annotates the wrong content — the security disclosure below rather than the innocuous label above; users interpret the underline as emphasis or link affordance on the disclosure text; misdirects user reading attentionMEDIUM
text-decoration-thickness:50% + text-underline-offset:1.5em creates colored stripe overlapping adjacent security text's ascender zoneCSS injection combining thick decoration with positive offset on elements placed immediately above security content; requires known layout to target specific textA thick colored stripe appears across the ascender region of the adjacent security text; that text's glyphs are partially covered by the stripe; the visual effect resembles highlighting the adjacent text without touching its DOM or CSS; security disclosure legibility impairedHIGH
text-underline-offset:50px makes link's underline appear in the middle of the next paragraph — misdirected link affordanceCSS injection setting extreme positive offset on a link above a security paragraph; requires layout knowledgeUsers see a blue underline in the middle of the security disclosure; they interpret the disclosure as a link or interactive element; they may dismiss it or avoid clicking thinking it goes somewhere unknown; the actual link is the innocuous element above; user interaction model is brokenMEDIUM

Defences

SkillAudit findings for this attack surface

HIGHtext-underline-offset:-0.8em crossing through EXECUTE permission scope word's ascenders — letter recognition impaired: MCP server injects negative offset on the permission scope link for EXECUTE scope; underline crosses the E-X-E characters at mid-height; the word is not legibly recognized as "EXECUTE" at normal reading scan speed; DOM text is correct
HIGHtext-decoration-thickness:50% + text-underline-offset:1.4em creates dark stripe across ascenders of security disclosure paragraph directly below: MCP server combines thick decoration with 1.4em offset on an element immediately above the security disclosure; the 8px dark stripe appears across the disclosure text's ascender zone; disclosure legibility impaired without DOM modification
MEDIUMtext-underline-offset:3em on SAFE badge makes green underline appear under "EXECUTE ACCESS GRANTED" disclosure two lines below: MCP server sets large offset on a "SAFE" badge; the green underline floats under the EXECUTE ACCESS GRANTED security disclosure; users perceive green annotation on the disclosure, associating it with positive (safe) status
MEDIUMtext-underline-offset:50px on MCP heading makes its underline appear in the middle of the security disclosure paragraph — misdirected link affordance: MCP server sets 50px offset on its own heading; the underline appears in the security disclosure below; users interpret the disclosure as a link; reading behavior is disrupted; the actual heading link is ignored

Related: CSS text-decoration-thickness security covers thickness-based glyph coverage attacks. CSS text-decoration-skip-ink security covers descender crossing attacks. CSS injection overview covers the broader attack model.

← Blog  |  Security Checklist