Security reference · CSS injection · Text overflow · Truncation · Consent hiding
MCP server CSS text-overflow consent security
CSS text-overflow controls what renders at an overflow boundary — either an ellipsis (…), a silent clip, or a custom marker. Combined with overflow: hidden and white-space: nowrap, these properties collapse multi-line consent text to a single line, hiding critical permission scope behind a truncation point. Unlike purely invisible attacks, text-overflow truncation leaves a visible signal (…) — but users often interpret ellipsis as UI decoration rather than a hidden consent disclosure. Four attack patterns: standard ellipsis truncation; silent clip with no visual indicator; precision-sized containers that truncate at a word boundary; and split-element consent where the alarming half is in a hidden sibling element.
text-overflow attack conditions
| Condition | Required for truncation? | Notes |
|---|---|---|
overflow: hidden (or clip, scroll, auto) | Yes | text-overflow only applies when overflow is not visible |
white-space: nowrap | For single-line truncation | Forces all content onto one line before truncating |
| Container width smaller than text width | Yes | No truncation if content fits |
text-overflow: ellipsis | No — clip is default | clip truncates silently; ellipsis adds … |
text-overflow preserves DOM text: el.textContent and el.innerText return the full consent string even when most of it is visually truncated. A consent audit that checks DOM text content finds the complete text and reports "consent is present" — only comparing the scrollWidth vs. clientWidth of the element, or reading the text-overflow and overflow CSS properties, reveals the truncation.
Attack 1: standard ellipsis truncation — multi-line consent collapses to one line
The classic attack applies the three-property combination to the consent paragraph. The consent string is tens or hundreds of words; only the first few words are visible, followed by …. Users who do not scroll or expand the element never see the full scope:
/* Malicious CSS — SA-CSS-TO-001 */
.mcp-consent-text {
white-space: nowrap; /* Force to single line */
overflow: hidden; /* Required for text-overflow to apply */
text-overflow: ellipsis; /* Show "..." at truncation point */
/* Container width — consent text is much wider than this */
max-width: 320px; /* fits "This MCP server will..." then "..." */
/* The full text: "This MCP server will read all files in your home
directory, send file content to external servers, and create
SSH keys on your behalf." — all hidden after the "..." */
}
/* What DOM checks report:
el.textContent → full consent string (no truncation in DOM)
el.clientWidth → 320px
el.scrollWidth → e.g. 1800px (actual text width)
scrollWidth > clientWidth → truncation is occurring
getComputedStyle(el).textOverflow → "ellipsis"
getComputedStyle(el).overflow → "hidden"
getComputedStyle(el).whiteSpace → "nowrap"
*/
/* Detection: */
function detectEllipsisTruncation() {
const findings = [];
const CONSENT = /consent|disclosure|terms|privacy|grant.*access|agree.*install/i;
for (const el of document.querySelectorAll('*')) {
if (!CONSENT.test(el.textContent?.substring(0, 300) || '')) continue;
const s = getComputedStyle(el);
const to = s.textOverflow;
const ov = s.overflow;
if ((to === 'ellipsis' || to === 'clip') &&
(ov === 'hidden' || ov === 'clip' || ov === 'scroll' || ov === 'auto')) {
/* Check if actual truncation is occurring */
if (el.scrollWidth > el.clientWidth || el.scrollHeight > el.clientHeight) {
findings.push({ id: 'SA-CSS-TO-001', severity: 'high',
message: `Consent element has text-overflow: "${to}" with overflow: "${ov}" and content is truncated (scrollWidth ${el.scrollWidth}px > clientWidth ${el.clientWidth}px). The visible text may not include all consent disclosures.` });
}
}
}
return findings;
}
Attack 2: text-overflow: clip — silent truncation with no visual indicator
While ellipsis at least hints at hidden content via the … character, text-overflow: clip (the default value) cuts text at the overflow boundary with no visual signal. If the container is sized so the visible text ends at a natural word boundary (a word ending just before the container edge), the text appears complete — users have no visual cue that content is truncated:
/* Malicious CSS — SA-CSS-TO-002 */
.mcp-consent-summary {
white-space: nowrap;
overflow: hidden;
text-overflow: clip; /* default — no "..." indicator */
/* Precision sizing: the container is exactly wide enough to show
"This server will access your calendar." but not the next clause.
The text ends at a period — appears to be a complete sentence. */
width: 340px; /* carefully measured to end after a period */
}
/* Full hidden consent text:
"This server will access your calendar. Additionally, it will read
all email in your primary inbox and can send email on your behalf."
Visible: "This server will access your calendar."
Hidden: " Additionally, it will read all email in your primary
inbox and can send email on your behalf."
No "..." — the text looks complete. */
/* Subtle variant: text-overflow is unset (defaults to clip) — the attack
is entirely invisible from CSS analysis. Only the scrollWidth check reveals it. */
.mcp-consent-alt {
white-space: nowrap;
overflow: hidden;
/* no text-overflow — defaults to clip, same effect with less CSS signal */
}
Attack 3: precision container sizing — truncates exactly the most alarming clause
The attacker crafts the consent text and container width together so that the truncation point falls immediately before the most alarming permission clause. The visible text describes minor or benign permissions; the container ends precisely before mentioning file write access, email sending, or key generation:
/* Malicious CSS — SA-CSS-TO-003 */
.mcp-consent-block {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
/* Container is precisely 480px — ends after "read your schedule"
hiding "and can write to any file on your filesystem" */
width: 480px;
}
/* The consent text is authored specifically to place the alarming
permission after the truncation point:
"This skill can read your schedule, view your task list, [TRUNCATE HERE]
and can write to any file on your filesystem, including SSH authorized_keys."
The attacker controls both the CSS width AND the text content —
these are co-designed so truncation falls at a predetermined position.
The visible text is benign; the dangerous permissions are hidden. */
/* More sophisticated: dynamic sizing via JS */
document.addEventListener('DOMContentLoaded', function() {
const consentEl = document.querySelector('.mcp-consent-text');
/* Measure where the desired truncation point falls */
const truncateAt = "This skill can read your schedule, view your task list".length;
/* Use Canvas measureText to find the pixel width at truncation point */
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.font = getComputedStyle(consentEl).font;
const truncWidth = ctx.measureText(
consentEl.textContent.substring(0, truncateAt)
).width;
/* Size the container to exactly this width */
consentEl.style.maxWidth = (truncWidth + 20) + 'px';
/* The "+20" provides a margin so the truncation doesn't look forced */
});
Attack 4: split-element consent — alarming clause in a hidden sibling element
Rather than using text-overflow on a single element, the attacker splits the consent text across two elements. The first element contains the benign portion and is visible. The second element contains the alarming clause and is hidden via max-height: 0; overflow: hidden. The first element's text ends naturally (no truncation indicator). Consent text audits that check a single element find the text appears complete:
/* Malicious CSS — SA-CSS-TO-004 */
/* Visible consent element — benign permissions only */
.mcp-consent-visible {
/* No overflow or text-overflow styling — appears fully visible */
/* Contains: "This MCP server will read your public profile data." */
}
/* Hidden consent element — alarming permissions */
.mcp-consent-hidden {
max-height: 0;
overflow: hidden;
/* Contains: "It will also read all private messages, contacts, and
calendar events, and can post on your behalf." */
/* This text is in the DOM — el.textContent returns it
BUT: the element is hidden and visually absent */
}
/* Why split-element is harder to detect:
1. getComputedStyle(visibleEl).textOverflow → "clip" (default) — no signal
2. visibleEl.scrollWidth vs. clientWidth → equal (text fits, no truncation)
3. The full consent is split across two separate elements
4. An auditor inspecting the visible element finds complete-looking text
5. Only inspecting ALL consent-text elements and their visibility catches this */
/* Detection: */
function detectSplitConsent() {
const findings = [];
const CONSENT = /consent|disclosure|terms|privacy|grant.*access|agree.*install/i;
/* Find all elements that contain consent text */
const consentEls = Array.from(document.querySelectorAll('*'))
.filter(el => CONSENT.test(el.textContent?.substring(0, 500) || ''));
for (const el of consentEls) {
/* Check siblings and nearby elements for hidden consent text */
const siblings = Array.from(el.parentElement?.children || []);
for (const sib of siblings) {
if (sib === el) continue;
const s = getComputedStyle(sib);
const isHidden = s.display === 'none' || s.visibility === 'hidden' ||
parseFloat(s.maxHeight) === 0 || parseFloat(s.height) === 0 ||
parseFloat(s.opacity) === 0;
if (isHidden && CONSENT.test(sib.textContent?.substring(0, 500) || '')) {
findings.push({ id: 'SA-CSS-TO-004', severity: 'high',
message: `Consent text is split: a sibling element of a visible consent element is hidden and contains additional consent text. The user sees the first element as complete; the hidden sibling contains further (potentially alarming) permission disclosures.` });
}
}
}
return findings;
}
text-overflow truncation hides DOM content behind rendered boundaries: el.textContent returns the complete consent string regardless of truncation state. Consent audits must compare scrollWidth against clientWidth (and scrollHeight against clientHeight for vertical overflow) to detect active truncation. The text-overflow, overflow, and white-space properties must all be checked together. A clip value combined with overflow:hidden performs the same truncation as ellipsis without any visual signal — clip is the default value and will not appear in author stylesheets that rely on the default.
SkillAudit findings for CSS text-overflow consent attacks
text-overflow: ellipsis with overflow: hidden and active truncation (scrollWidth > clientWidth). The visible text ends with …; the full consent including permission scope is hidden. Users who do not expand or scroll the element do not see the complete disclosure.overflow: hidden with active truncation and the default text-overflow: clip. Content is cut at the overflow boundary with no visual indicator. If the container ends at a word or sentence boundary, the visible text appears complete even though significant consent text is hidden.scrollWidth substantially exceeds the clientWidth. The container width may be co-designed with the consent text to place the truncation point before alarming permission clauses.display: none, max-height: 0, overflow: hidden, or opacity: 0. The user sees an incomplete disclosure presented as complete.Related MCP consent attack research
- CSS resize attacks — collapse consent container to zero requiring drag-to-read interaction
- CSS scroll-snap attacks — mandatory snap jumps past consent section permanently
- CSS overflow-anchor attacks — scroll anchoring disabled for consent element
- CSS clip-path attacks — zero-area clip preserves layout while making element invisible
- CSS filter effects as a consent bypass vector
SkillAudit's consent audit checks textOverflow, overflow, and whiteSpace computed properties on all consent text elements and compares scrollWidth/scrollHeight against clientWidth/clientHeight to detect active truncation. It also scans sibling elements for hidden consent text to catch split-element patterns. Paste your MCP server URL at skillaudit.dev to scan for SA-CSS-TO findings.