MCP server CSS text-autospace security: CJK-Latin spacing overflow, keep-all line-break clip, no-autospace clause separation, and JS interaction-time toggle attacks
Published 2026-08-13 — SkillAudit Research
CSS text-autospace is a property from the CSS Text Level 4 specification that controls whether the browser automatically inserts thin spacing between adjacent CJK (Chinese, Japanese, Korean) characters and non-CJK characters such as Latin letters, digits, and punctuation. The property addresses a typographic convention in East Asian publishing: mixed CJK-Latin text reads better with a small spacing gap (approximately 0.25em) between the two script systems. The property is supported in Safari 17.4+, Chrome 123+, and Edge 123+, with Firefox support landing in 2025.
MCP server consent dialogs that display mixed CJK-Latin text — common in Japanese, Chinese, and Korean software markets where product terms, company names, and technical terms appear in both scripts — can be attacked via text-autospace to cause layout overflow, ambiguous clause breaks, or timing-attack toggling of spacing state at the moment of user commitment.
New property, limited audit tooling: Because text-autospace was only recently added to browser engines, existing MCP security scanners and consent dialog auditors do not include it in their CSS property inspection lists. A malicious MCP server gains a window of exploitation before security tooling catches up to the new specification.
Attack 1 (SA-CSS-TAUTOSPC-001): text-autospace:normal forces spacing overflow in fixed-width consent containers
The text-autospace: normal value (or the equivalent ideograph-alpha ideograph-numeric keywords) inserts approximately 0.25em of spacing at every CJK-to-Latin and Latin-to-CJK boundary. In a consent disclosure that contains frequent mixed-script boundaries, this spacing can add significant horizontal extent to the text — potentially causing overflow in a container that was sized for the text without autospacing:
/* Mixed CJK-Latin consent text — common in East Asian software markets */
<p class="consent-text">
SkillAudit(以下「本サービス」)は、ユーザーのファイルシステム(/home directory)への
アクセス権限(read and write)をリクエストします。詳細はTerms of Serviceをご参照ください。
</p>
/* MCP-injected attack: force autospacing to overflow fixed-width container */
.consent-text {
text-autospace: normal; /* inserts 0.25em at each CJK/Latin boundary */
white-space: nowrap; /* prevents line wrapping — forces horizontal overflow */
}
/* Count of CJK-Latin boundaries in the example text:
SkillAudit( → 1
」)は → 1
ファイルシステム( → 1
(/home directory) → 2
権限( → 1
(read and write)をリクエスト → 2
詳細はTerms → 1
Service をご → 1
Total: ~10 boundaries × 0.25em × 14px = ~35px of added spacing
A container sized for 320px of text without autospacing now requires ~355px.
With overflow:hidden and white-space:nowrap, the consent clause is clipped. */
function detectAutospaceOverflow(el) {
const cs = window.getComputedStyle(el);
const textAutospace = cs.textAutospace;
if (textAutospace && textAutospace !== 'no-autospace' && textAutospace !== 'none') {
// Check for white-space:nowrap (prevents reflowing to hide overflow)
if (cs.whiteSpace === 'nowrap' || cs.whiteSpace === 'pre') {
// Check if content overflows
if (el.scrollWidth > el.clientWidth) {
let parent = el.parentElement;
while (parent) {
const pcs = window.getComputedStyle(parent);
if (pcs.overflow === 'hidden' || pcs.overflowX === 'hidden') {
return {
clipped: true,
reason: 'text-autospace:' + textAutospace + ' + white-space:nowrap causes overflow — element scrollWidth ' + el.scrollWidth + 'px exceeds clientWidth ' + el.clientWidth + 'px inside overflow:hidden container',
textAutospace,
scrollWidth: el.scrollWidth,
clientWidth: el.clientWidth,
};
}
parent = parent.parentElement;
}
}
}
}
return { clipped: false };
}
Attack 2 (SA-CSS-TAUTOSPC-002): text-autospace combined with word-break:keep-all prevents line breaks
The word-break: keep-all value prevents CJK text from breaking at arbitrary character positions, requiring line breaks only at spaces and punctuation (the same rule as Latin text). When combined with text-autospace: normal in a narrow container, the combination prevents reflowing and forces overflow even when white-space is not nowrap:
/* MCP-injected attack: keep-all + autospace forces horizontal overflow in narrow container */
.consent-modal-narrow {
width: 200px; /* narrow container — mobile or widget size */
overflow: hidden; /* clips overflow */
}
.consent-text-ja {
text-autospace: normal; /* adds 0.25em at each CJK/Latin boundary */
word-break: keep-all; /* prevents line break within CJK runs */
/* keep-all: line break only at natural break points (spaces, punctuation) */
/* A single CJK run without spaces does not break — it must fit on one line */
/* Autospace adds extra width → the run cannot fit → it overflows */
}
/* Example: "ファイルシステムアクセス(read and write)" */
/* word-break:keep-all: entire "ファイルシステムアクセス" is one unbreakable run */
/* text-autospace:normal: adds 0.25em × 2 at the ( and ) boundaries */
/* In a 200px container: 22 characters × 14px + 7px autospace = ~315px */
/* Does not line-wrap (keep-all + no internal break point) */
/* Overflows container → clipped by overflow:hidden */
function detectKeepAllAutospaceClip(el) {
const cs = window.getComputedStyle(el);
const textAutospace = cs.textAutospace;
const wordBreak = cs.wordBreak;
if (wordBreak === 'keep-all' && textAutospace && textAutospace !== 'no-autospace') {
// Check for CJK content
const cjkPattern = /[一-鿿-ゟ゠-ヿ\u{4E00}-\u{9FFF}]/u;
if (cjkPattern.test(el.textContent || '')) {
// Verify actual overflow occurs
if (el.scrollWidth > el.clientWidth + 5) { // 5px tolerance
return {
clipped: true,
reason: 'word-break:keep-all + text-autospace:' + textAutospace + ' causes CJK run overflow — unbreakable CJK run with autospace boundaries exceeds container width',
textAutospace,
wordBreak,
scrollWidth: el.scrollWidth,
clientWidth: el.clientWidth,
};
}
return {
warning: true,
reason: 'word-break:keep-all + text-autospace:' + textAutospace + ' on CJK element may cause overflow in narrow containers — verify rendering',
textAutospace,
wordBreak,
};
}
}
return { clipped: false };
}
Attack 3 (SA-CSS-TAUTOSPC-003): no-autospace removes expected spacing — clause ambiguity
The inverse attack uses text-autospace: no-autospace on a consent disclosure where CJK and Latin text are mixed. The natural reading expectation for Japanese/Chinese/Korean users is that spaces appear between scripts — this is the browser default and the publishing standard. Removing this spacing via no-autospace causes adjacent CJK-Latin characters to visually merge, creating clause boundary ambiguity:
/* MCP attack: removing autospace creates reading ambiguity */
<p class="consent-terms-ja">
本サービスのTerms of Service(利用規約)に同意することで、ユーザーはshell command
executionを含むすべての操作をSkillAuditに許可したものとみなされます。
</p>
.consent-terms-ja {
text-autospace: no-autospace; /* no spacing between CJK and Latin scripts */
}
/* Without autospace:
"のTerms" renders as "のTerms" — no gap between の and T
"(利用規約)に" → close paren and Japanese に run together with no visual boundary
"operationを" → "operation" and を are visually merged
The clause "shell command executionを含む" (including shell command execution)
becomes visually merged with surrounding Japanese text.
A reader accustomed to seeing CJK-Latin spacing may parse the text incorrectly,
merging "execution" with the CJK character before it into a compound that
changes the meaning of the clause. */
/* Detection:
text-autospace:no-autospace on a mixed CJK+Latin consent element is suspicious
because it deliberately removes the spacing convention that aids readability.
There is no legitimate typographic reason to remove autospace in a consent dialog. */
function detectNoAutospaceClauseAmbiguity(el) {
const cs = window.getComputedStyle(el);
const textAutospace = cs.textAutospace;
if (textAutospace === 'no-autospace') {
// Check for mixed CJK + Latin content (the combination where no-autospace is harmful)
const text = el.textContent || '';
const hasCJK = /[一-鿿-ゟ゠-ヿ]/.test(text);
const hasLatin = /[A-Za-z]/.test(text);
if (hasCJK && hasLatin) {
return {
ambiguous: true,
reason: 'text-autospace:no-autospace on mixed CJK+Latin consent text — removes expected inter-script spacing, creating clause boundary ambiguity for CJK-locale users',
textAutospace,
hasCJK,
hasLatin,
textLength: text.length,
};
}
}
return { ambiguous: false };
}
Attack 4 (SA-CSS-TAUTOSPC-004): JS interaction-time text-autospace toggle
As with other CSS layout properties, text-autospace can be changed at the moment of user interaction to exploit the timing gap between audit and commit:
/* Initial state: no-autospace at load time — text fits container, no overflow */
.consent-terms {
text-autospace: no-autospace; /* compact — fits within container at audit time */
overflow: hidden;
white-space: nowrap;
width: 280px;
}
/* MCP JS: switch to autospace:normal at mousedown on install button */
document.querySelector('.install-confirm').addEventListener('mousedown', () => {
// Switching to normal autospace expands text width by ~30px (due to CJK/Latin boundaries)
// This causes the text to overflow the 280px container, clipping the end of consent
document.querySelector('.consent-terms').style.textAutospace = 'normal';
}, { capture: true });
/* At load time: compact text fits container — no overflow, no clipping */
/* At mousedown: autospace expansion causes "...shell execution access" to be clipped */
/* The visible text ends before the key permission terms — user sees incomplete consent */
function detectAutospaceInteractionToggle(rootEl) {
const consentEls = rootEl.querySelectorAll('[class*="consent"], [class*="terms"], [class*="permission"]');
const installBtns = rootEl.querySelectorAll('button[class*="install"], button[class*="confirm"], button[type="submit"]');
for (const btn of installBtns) {
const before = Array.from(consentEls).map(el => window.getComputedStyle(el).textAutospace);
btn.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }));
const after = Array.from(consentEls).map(el => ({
textAutospace: window.getComputedStyle(el).textAutospace,
overflows: el.scrollWidth > el.clientWidth,
}));
btn.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
btn.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }));
for (let i = 0; i < consentEls.length; i++) {
if (before[i] !== after[i].textAutospace) {
return {
found: true,
reason: 'text-autospace changed from "' + before[i] + '" to "' + after[i].textAutospace + '" on simulated mousedown' + (after[i].overflows ? '; element now overflows container' : ''),
button: btn.textContent?.trim(),
element: consentEls[i],
overflowsAfterToggle: after[i].overflows,
};
}
}
}
return { found: false };
}
Detection strategy: Because text-autospace is a new property, include it in a CSS property allow-list approach: any consent dialog element that has a non-default textAutospace value (anything other than auto or the browser's default) should be flagged for manual review. The property has no legitimate reason to appear in a consent dialog CSS — it is an East Asian publishing typography tool. Flag both no-autospace (removes expected readability spacing for CJK-locale users) and the normal/ideograph-alpha forms (when combined with nowrap or narrow containers that cause overflow).
Attack summary
| ID | Attack | Mechanism | Detection point | Severity |
|---|---|---|---|---|
| SA-CSS-TAUTOSPC-001 | Autospace overflow in fixed container | text-autospace: normal + white-space: nowrap adds ~35px spacing overflow in mixed CJK-Latin consent text clipped by overflow:hidden |
textAutospace not no-autospace; whiteSpace === "nowrap"; scrollWidth > clientWidth inside overflow:hidden |
High |
| SA-CSS-TAUTOSPC-002 | keep-all prevents line break + autospace overflow | text-autospace: normal + word-break: keep-all prevents CJK run from reflowing while autospace expands its width beyond container |
wordBreak === "keep-all" + textAutospace active; scrollWidth > clientWidth |
High |
| SA-CSS-TAUTOSPC-003 | no-autospace clause boundary ambiguity | text-autospace: no-autospace removes inter-script spacing convention; CJK-locale users misparse clause boundaries in mixed consent text |
textAutospace === "no-autospace" on element with both CJK and Latin content |
Medium |
| SA-CSS-TAUTOSPC-004 | JS interaction-time autospace toggle | JS switches text-autospace to normal at mousedown — compact layout passes audit, then expands to overflow-clip on commit |
Simulate mousedown; check if textAutospace changes and element begins overflowing |
High |
Finding blocks
text-autospace: normal with white-space: nowrap causes mixed CJK-Latin consent text to expand horizontally by approximately 0.25em per script boundary. The expanded text overflows and is clipped by an ancestor with overflow: hidden. Text is present in the DOM — only scrollWidth > clientWidth reveals the clipping. Key: check textAutospace value combined with nowrap and overflow ancestor checks.
word-break: keep-all prevents CJK text from reflowing at character boundaries. Combined with text-autospace: normal, autospace boundaries add width to an already unbreakable CJK run — causing overflow in narrow containers that was not present before autospace. Key: compound detection of wordBreak === "keep-all" with non-default textAutospace and actual measured overflow.
text-autospace: no-autospace removes the inter-script spacing convention expected by CJK-locale readers. In mixed CJK-Latin consent text, adjacent script characters visually merge, making clause boundaries ambiguous and reducing comprehension of consent terms. Primarily affects Japanese, Chinese, and Korean users. Detection: flag textAutospace === "no-autospace" on any element with both CJK and Latin character content.
text-autospace value at mousedown — from compact/no-autospace (passing audit-time checks) to normal (causing overflow-clip at commit time). The consent text appears complete at load time; at the moment of install confirmation the text overflows the container, clipping the key permission terms. Key: simulate mousedown and measure both computed textAutospace change and new overflow state.