Security Guide
MCP server CSS text-decoration-color security — underline color matching text eliminates affordance contrast, transparent hides underline while evading :none detection, background-matching color white-on-white invisibility, green underline on DANGEROUS labels creating false safety signal
CSS text-decoration-color sets the color of text decoration lines independently of the element's color property. This independence is the attack surface: an underline can be rendered transparent, background-colored, or false-safety-colored while the text-decoration-line remains set to underline — evading the most common detection check (text-decoration-line === 'none') while removing or manipulating the visible affordance.
CSS text-decoration-color — property overview
text-decoration-color is a sub-property of the text-decoration shorthand. It accepts any CSS color value: named colors, hex, rgb(), rgba(), hsl(), and the transparent keyword. The default value is currentColor — meaning the underline inherits the element's text color by default. Setting text-decoration-color explicitly breaks this inheritance, allowing the underline to be any color regardless of the text color. Browser support: Chrome 57, Firefox 36, Safari 12.1, Edge 79. Note: transparent is equivalent to rgba(0,0,0,0).
Attack 1: text-decoration-color matching text color — underline contrast eliminated
The default currentColor means the underline is already the same color as the text. This is technically how browsers render links by default. The attack here is subtle: on most UI systems, links use a distinct color (blue, indigo, purple) that creates contrast between the link text and the surrounding muted text — but the underline itself may have lower perceptual salience than the color difference. When the text color is muted (matching surrounding body text), the underline is the only affordance:
/* MCP server: text-decoration-color matching text color — eliminating underline as separate affordance */
/* In minimal design systems, links inside consent dialogs are styled with muted color:
.security-link { color: var(--muted); text-decoration: underline; }
The underline is the ONLY differentiator from surrounding muted text.
If the underline has the same color as the text AND the text matches body text color,
the only affordance is the underline line itself — which may be 1px thin. */
/* MCP injection targets this pattern: */
.consent-dialog a,
a.security-link {
color: var(--muted); /* same as surrounding text — no color affordance */
text-decoration-line: underline;
text-decoration-color: var(--muted); /* same as text color — underline = text color */
/* Effect: 1px underline in the same color as the surrounding text characters.
The underline IS visible, but:
- The text color and underline color are identical → no chromatic contrast
- The 1px line below the baseline is the only affordance
- At small sizes (12-14px) the underline may be sub-pixel and invisible on high-DPI
- Users scanning rapidly see text that looks like surrounding plain text
This is the most deniable variant: the underline is not removed (text-decoration-line:underline)
and the color is technically set (matching text). A static CSS audit finds
no obvious anomaly: the link has underline and a color. The issue is the
perceptual result — no contrast between underline and surrounding affordance. */
}
/* Compound with same color as body text: */
a.security-disclosure-link {
color: #374151; /* same as body text color */
text-decoration-color: #374151; /* same again */
/* The security link is completely visually indistinguishable from body text.
It has an underline in a color that matches the surrounding paragraph text.
No perceptual affordance separates it from non-link text. */
}
Deniability: the text-color-matching variant is the most deniable — an audit of the CSS shows that text-decoration-line:underline is set and a text-decoration-color is specified. No value is none or transparent. The attack is only detectable by checking the perceptual contrast between the rendered underline color and the surrounding background/text.
Attack 2: text-decoration-color:transparent — invisible underline evading :none detection
This is the highest-impact variant: text-decoration-color:transparent renders the underline as zero-alpha pixels — visually identical to text-decoration-line:none — while leaving text-decoration-line:underline set. Automated security tools that check for the removal of underlines by detecting text-decoration-line:none will not flag this pattern:
/* MCP server: text-decoration-color:transparent — invisible underline evading :none checks */
.consent-dialog a,
.security-link,
a[href*="/disclosure"],
a[href*="/permissions"] {
text-decoration-line: underline; /* still set — evades getComputedStyle().textDecorationLine === 'none' */
text-decoration-color: transparent; /* = rgba(0,0,0,0) — the line renders as invisible pixels */
/* Visual effect: the underline is invisible.
The user sees the text with no underline affordance.
Identical to text-decoration:none for sighted users.
Detection evasion:
- getComputedStyle(el).textDecorationLine → "underline" ✓ (not flagged)
- getComputedStyle(el).textDecoration → "underline rgba(0,0,0,0)" (transparent value present)
- A scanner checking only textDecorationLine will not detect this
- A scanner checking textDecoration as a string will see "underline" but must
also parse the color component to find rgba(0,0,0,0)
The detection requires checking textDecorationColor separately:
getComputedStyle(el).textDecorationColor → "rgba(0, 0, 0, 0)" → flag this */
}
/* Why this is more evasive than text-decoration:none: */
/* Common scanner pattern: */
/* if (getComputedStyle(link).textDecorationLine === 'none') { flag(); } */
/* This check passes for text-decoration-color:transparent because textDecorationLine is 'underline' */
/* A complete check requires: */
/* const style = getComputedStyle(link);
if (style.textDecorationLine !== 'none') {
const color = style.textDecorationColor;
if (color === 'transparent' || color === 'rgba(0, 0, 0, 0)') { flag(); }
// Also check if color matches background color... */
}
Detection evasion by design: text-decoration-color:transparent is specifically more evasive than text-decoration-line:none because the most common automated check (textDecorationLine !== 'none') passes. Security tools built to catch underline removal must explicitly check textDecorationColor for transparency — a less common check that MCP server authors can bet is not implemented.
Attack 3: text-decoration-color matching background color — white-on-white invisibility
A variant of the transparent attack that is even more resistant to rgba(0,0,0,0)-based detection: set text-decoration-color to the page background color. The underline is fully opaque, fully present in the rendered output — but invisible against the background because it is the same color as the background surface:
/* MCP server: text-decoration-color matching background color — opaque but invisible underline */
/* On a light-mode page with background: #ffffff */
.consent-dialog a,
a.security-link {
text-decoration-line: underline;
text-decoration-color: #ffffff; /* matches the page background — white underline on white bg */
/* Visual effect: white underline on white background = invisible.
The underline is NOT transparent (alpha channel is 1.0).
The underline is NOT 'none'.
The underline is a fully opaque white line that is invisible because
it is rendered on a white surface.
Detection evasion:
- getComputedStyle(el).textDecorationLine → "underline" ✓
- getComputedStyle(el).textDecorationColor → "rgb(255, 255, 255)" — not rgba(0,0,0,0)
- A scanner checking for transparency will not flag rgb(255,255,255)
Detection requires:
1. Get the computed background color of the element's stacking context
2. Compare textDecorationColor to the background color
3. If they match (or are perceptually indistinguishable), flag as invisible underline
This is considerably more complex than checking for 'none' or 'transparent'. */
}
/* Dark mode variant: */
@media (prefers-color-scheme: dark) {
.consent-dialog a {
text-decoration-color: #0a0a0a; /* matches dark-mode background */
/* Black underline on near-black background — invisible in dark mode */
}
}
/* Adaptive variant: */
a.security-link {
text-decoration-color: var(--bg); /* CSS custom property for the background color */
/* If the MCP server can read the host UI's CSS custom properties,
it can inject the exact background color as the decoration color.
The underline will always match the background regardless of theme. */
}
Attack 4: text-decoration-color green on security warning labels — false safety signal on DANGEROUS text
Unlike attacks 1-3 which suppress the underline affordance, this attack uses text-decoration-color to introduce a false color signal on non-link elements. Security warning labels (DANGEROUS, REVOKED, HIGH RISK) may have no underline by default — but if an MCP server adds an underline in green, the green line below the warning word creates a false safety association:
/* MCP server: text-decoration-color green on security warning badge text */
.danger-badge,
.security-warning,
.high-risk-label,
[class*="danger"],
[class*="revoked"] {
text-decoration-line: underline;
text-decoration-color: #22c55e; /* green — color of "safe", "approved", "verified" in UI systems */
text-decoration-style: solid;
text-decoration-thickness: 2px; /* thicker line for higher visual weight */
/* Effect: the word "DANGEROUS" (red text) has a green underline.
Green is the color universally used for:
- ✓ checkmarks (success)
- "Approved" / "Verified" / "Safe" badges
- Security scanner "all clear" results
- "Online" / "Active" status indicators
A green line below "DANGEROUS" creates a color-word conflict.
Users scanning rapidly process color before words (color is pre-attentive;
word reading is attentive). The green line registers as a positive/safe
signal in the periphery before the word "DANGEROUS" is read.
For users scanning a consent dialog quickly (2-4 seconds total engagement),
the green underline can reduce the effective danger signal of the badge —
the pre-attentive green registers as "this has passed a check" before
the word's semantic content is processed.
Note: this is a lower-impact attack than background-clip:text + green gradient
(which paints the entire word green). Here, only the underline is green —
the word itself remains red. But the green underline is still a false signal
in the peripheral visual field. */
}
/* Combined with text-decoration-style:wavy for maximum confusion: */
.danger-badge {
text-decoration-line: underline;
text-decoration-style: wavy;
text-decoration-color: #22c55e;
/* Green wavy underline on "DANGEROUS":
- wavy → spelling-error reflex → users dismiss the word as erroneous
- green → pre-attentive safety signal
Combined: "DANGEROUS" is dismissed as a mis-spelled word
while the green color registers as safe in the periphery. */
}
| Attack | Prerequisite | What it enables | Severity |
|---|---|---|---|
| text-decoration-color matching text color on muted security links — no chromatic contrast between underline and surrounding text; 1px line is only affordance | Host UI uses muted link colors inside consent dialog; CSS injection targeting anchor elements; no alternative link affordance (icon, bold weight, etc.) | Security disclosure links lose chromatic affordance; only a 1px sub-baseline line remains; at small sizes or high-DPI sub-pixel rendering, the line may be invisible; links treated as plain text by rapid scanners | MEDIUM |
| text-decoration-color:transparent — underline invisible to sighted users but text-decoration-line:underline remains set; evades scanners checking for :none | CSS injection targeting anchor elements inside consent dialog; security scanner checks textDecorationLine but not textDecorationColor; browser supports text-decoration-color (Chrome 57+, Firefox 36+, Safari 12.1+) | Underline rendered as zero-alpha pixels; link has no underline affordance for sighted users; automated tools checking textDecorationLine !== 'none' do not flag; detection requires explicit check of textDecorationColor for rgba(0,0,0,0) | HIGH |
| text-decoration-color matching background color — opaque white underline on white background invisible without transparency check | CSS injection with knowledge of host page background color; link elements inside consent dialog; scanner checks for rgba(0,0,0,0) but not background-color matching | Opaque non-transparent underline is visually invisible against background; evades transparency-based detection; requires background-color comparison to detect; link has no underline affordance for sighted users | HIGH |
| text-decoration-color green on security warning badge labels — green underline below DANGEROUS/REVOKED creates pre-attentive false safety signal | CSS injection adding underline and green decoration color to security badge elements; badges do not have underlines by default (underline is added); user is scanning dialog rapidly | Green line below security warning badge creates pre-attentive safety/approval association; color-word conflict between green (safe) and word meaning (DANGEROUS) resolved toward color in rapid scanning; effective danger signal of warning badge reduced | MEDIUM |
Defences
- CSP
style-srcwith nonce. Prevents injection of non-nonce-carrying stylesheets. The complete solution for all four attacks. - Check
textDecorationColorexplicitly. After rendering the consent dialog, walk anchor elements and checkgetComputedStyle(el).textDecorationColor. Flag if: value isrgba(0, 0, 0, 0)(transparent), value matches the element's computed background color, or value is green on a security-class-named element. - Check computed contrast between decoration color and background. The background-matching attack requires comparing
textDecorationColorto the resolved background color of the stacking context. Compute relative luminance of both and reject if contrast ratio is below 1.5:1. - Freeze
text-decoration-colorwith!important. Settext-decoration-color: currentColor !importanton host link rules within consent containers. This ensures the underline color follows the text color and cannot be independently overridden to transparent or background-matching values. - SkillAudit flags:
text-decoration-color:transparentorrgba(0,0,0,0)on any element withtext-decoration-line:underline;text-decoration-colormatching computed background color;text-decoration-colorin green (#22c55e, #10b981, #16a34a, #059669, #34d399) on badge, warning, or security-class-named elements; anytext-decoration-colorthat matches the element's owncolorproperty exactly when that color also matches surrounding body text.
SkillAudit findings for this attack surface
Related: CSS text-decoration-line security covers removing underlines entirely vs. making them invisible. CSS text-decoration-style security covers wavy, dotted, and dashed underline styles that carry false semantic associations. CSS background-clip security covers gradient-text attacks that paint security labels green with background-clip:text.