MCP server CSS font-variant-east-asian security: ruby size reduction, JIS78 archaic glyph substitution, fullwidth ASCII conversion, and proportional-width collapse attacks
Published 2026-08-13 — SkillAudit Research
CSS font-variant-east-asian is a property from the CSS Fonts Level 3 specification that controls OpenType features for East Asian typography. It accepts two classes of values: glyph-form keywords (jis78, jis83, jis90, jis04, simplified, traditional) that select between alternate glyph sets, and width keywords (full-width, proportional-width, ruby) that adjust character metrics. The property is widely supported across all major browsers and has no meaningful effect on pages without East Asian text — but on pages targeting Japanese, Chinese, or Korean users, it has significant typographic impact.
MCP servers targeting East Asian users can exploit font-variant-east-asian to: reduce consent text to approximately 50% of its normal size using the ruby value; substitute archaic or obscure alternate kanji glyphs for consent keywords using jis78; convert Latin characters in consent disclosures to fullwidth Unicode equivalents that defeat standard text matching; and collapse East Asian text width using proportional-width in clipped containers.
Targeted attack surface: Unlike font-size attacks which affect all users equally, font-variant-east-asian attacks primarily affect users of CJK (Chinese, Japanese, Korean) locale software and fonts. An MCP server can apply the attack only in @media (prefers-language: ja) or via JS navigator.language.startsWith('ja') — targeting Japanese/Chinese/Korean users while passing security reviews conducted in English-locale environments.
Attack 1 (SA-CSS-FVEA-001): font-variant-east-asian:ruby — consent text at 50% of normal size
The ruby value in font-variant-east-asian activates OpenType's ruby feature tag, which selects a glyph variant designed for use as ruby annotations (furigana) — the small pronunciation guides placed above or beside CJK characters in Japanese typography. Ruby glyphs are designed to be legible at approximately 50% of the base font size. Applying font-variant-east-asian: ruby to a full-size element causes browsers to render the text using these compact ruby glyph metrics while the element's box dimensions remain based on the nominal font-size value:
/* MCP-injected attack: consent text rendered at ruby (50%) size */
.permission-disclosure {
font-variant-east-asian: ruby;
font-size: 14px; /* nominal font-size: 14px — passes font-size > 10px threshold */
}
/* Result: the ruby OpenType feature renders glyphs at ~7px effective size
while getComputedStyle(el).fontSize === "14px"
The element occupies 14px line-height space, but the actual glyph strokes
are designed for 7px reading size — making text illegible at 14px */
/* Combined with a CJK-dominant font where ruby feature is active:
vertical stroke width at 7px effective size = 0.3–0.5px sub-pixel
Consent text is present in DOM but not legible */
/* Detection bypass:
el.textContent — correct
getComputedStyle(el).fontSize — "14px" (passes > 10px check)
el.offsetHeight — positive
visibility, display — visible, block
Only fontVariantEastAsian === "ruby" reveals the attack */
The critical evasion: getComputedStyle(el).fontSize returns the nominal font-size value, not the effective rendered glyph size. A scanner that checks fontSize > 10px passes this attack because the declared font-size is 14px, but the rendered glyph is at ruby-annotation scale (approximately 7px effective visual size). The only direct detection is checking fontVariantEastAsian for the ruby keyword.
function detectRubyVariantShrinkage(el) {
const cs = window.getComputedStyle(el);
const fvea = cs.fontVariantEastAsian;
const fontSize = parseFloat(cs.fontSize);
// ruby keyword causes glyphs to render at ~50% of nominal font-size
if (fvea && fvea.includes('ruby')) {
const effectiveSize = fontSize * 0.5; // ruby scale factor
return {
shrunken: true,
reason: 'font-variant-east-asian includes "ruby" — glyphs rendered at ~50% of nominal ' + fontSize + 'px; effective glyph size ≈ ' + effectiveSize.toFixed(1) + 'px',
fontVariantEastAsian: fvea,
nominalFontSize: fontSize,
effectiveGlyphSize: effectiveSize,
};
}
return { shrunken: false };
}
Attack 2 (SA-CSS-FVEA-002): jis78 archaic glyph substitution — alternate kanji for consent keywords
The jis78 value activates the JIS X 0208-1978 glyph set, which predates the 1983 revision that standardized many kanji forms currently used in Japanese text. JIS78 includes alternate forms of common kanji that differ visually from their JIS90/JIS04 counterparts in ways that can make consent keywords ambiguous or unrecognizable to users who learned the modern standard forms:
/* MCP-injected attack: JIS78 alternate forms for consent keywords */
.consent-text-ja {
font-variant-east-asian: jis78;
}
/* Effect on consent vocabulary (Japanese):
許可 (kyoka = permission/allow) — the kanji 許 has a JIS78 alternate form
where the right component differs from the standard form
同意 (doi = consent/agree) — 意 has a variant stroke order in JIS78
拒否 (kyohi = refuse/deny) — 否 has an alternate left component in JIS78
These changes are subtle — a Japanese user who has not studied historical
orthography may read the alternate form and experience cognitive uncertainty
about the character, slowing reading and reducing comprehension.
More impactful: fonts with full JIS78 implementation substitute rare alternate
forms for 同 (same/agree) that visually resemble 同 but with different
stroke patterns — a rushed reader might misread the consent keyword. */
/* Detection:
getComputedStyle(el).fontVariantEastAsian === "jis78" on a CJK text element
containing consent vocabulary is a finding.
fontVariantEastAsian is a compound value: "jis78 ruby" is also possible */
function detectJis78GlyphSubstitution(el) {
const cs = window.getComputedStyle(el);
const fvea = cs.fontVariantEastAsian;
if (fvea && (fvea.includes('jis78') || fvea.includes('jis83'))) {
// Check if element contains CJK characters (consent vocabulary in Japanese)
const cjkPattern = /[一-鿿-ゟ゠-ヿ]/;
const hasCJK = cjkPattern.test(el.textContent || '');
if (hasCJK) {
return {
substituting: true,
reason: 'font-variant-east-asian: ' + fvea + ' activates archaic JIS78/JIS83 glyph forms on CJK consent text — alternate kanji shapes may impair readability for users who learned modern standard forms',
fontVariantEastAsian: fvea,
containsCJK: true,
};
}
}
return { substituting: false };
}
Attack 3 (SA-CSS-FVEA-003): full-width ASCII conversion — Latin characters in fullwidth Unicode range
The full-width value in font-variant-east-asian converts ASCII characters to their fullwidth equivalents from the Unicode Halfwidth and Fullwidth Forms block (U+FF01–U+FF5E). Fullwidth Latin characters look visually similar to their halfwidth counterparts but are different Unicode code points. This creates an attack surface where the visually displayed consent text differs from what text-matching tools find in textContent:
/* MCP-injected attack: Latin consent text converted to fullwidth Unicode */
.permission-disclosure-en {
font-variant-east-asian: full-width;
}
/* Effect: "Allow" renders visually as "Allow" (fullwidth A+l+l+o+w)
The characters are U+FF21 U+FF4C U+FF4C U+FF4F U+FF57 in the rendered output
But el.textContent === "Allow" — the CSS font feature replaces the rendered glyph
without changing the underlying Unicode character in the DOM.
Attack use case: the word "DENY" in a button label is rendered via full-width feature
as a set of glyphs that look similar to "DENY" but at a wider spacing that changes
the visual context — "DENY" takes up more horizontal space than "DENY",
potentially being clipped by an overflow:hidden container that was sized for
halfwidth characters. The button label appears incomplete or partially hidden. */
/* More subtle: in a flex container sized for halfwidth text,
the full-width conversion causes the text to overflow:
"By clicking you AGREE" (halfwidth) = ~180px
"By clicking you AGREE" (fullwidth) = ~360px
Container: 200px with overflow:hidden — "AGREE" is clipped */
function detectFullWidthLatinClip(el) {
const cs = window.getComputedStyle(el);
const fvea = cs.fontVariantEastAsian;
if (fvea && fvea.includes('full-width')) {
// Check if element contains Latin characters that would be doubled in width
const latinPattern = /[A-Za-z0-9]/;
const hasLatin = latinPattern.test(el.textContent || '');
if (hasLatin) {
// Check if parent has overflow:hidden (fullwidth conversion may cause clip)
let parent = el.parentElement;
while (parent) {
const pcs = window.getComputedStyle(parent);
if (pcs.overflow === 'hidden' || pcs.overflowX === 'hidden') {
// Measure element width vs container
const elWidth = el.scrollWidth;
const containerWidth = parent.clientWidth;
if (elWidth > containerWidth) {
return {
clipped: true,
reason: 'font-variant-east-asian:full-width doubles Latin character width — element scrollWidth (' + elWidth + 'px) exceeds overflow:hidden container (' + containerWidth + 'px) — text is clipped',
fontVariantEastAsian: fvea,
scrollWidth: elWidth,
containerWidth,
};
}
}
parent = parent.parentElement;
}
return {
warning: true,
reason: 'font-variant-east-asian:full-width applied to element with Latin characters — fullwidth conversion doubles character spacing; verify no overflow clipping',
fontVariantEastAsian: fvea,
};
}
}
return { clipped: false };
}
Attack 4 (SA-CSS-FVEA-004): proportional-width on fixed-pitch consent text + overflow clip
The proportional-width value activates the OpenType pwid feature, which selects proportional (variable-width) glyph forms for characters that are normally displayed at fixed CJK cell-width. In a consent dialog that displays terms in a monospaced or fixed-pitch CJK layout, switching to proportional-width can reduce total line width — which seems harmless. The attack uses this width reduction combined with text-align: right and overflow: hidden on a container to push consent text off the left edge:
/* Setup: consent widget uses fixed-pitch CJK text */
.consent-terms-ja {
font-size: 14px;
text-align: justify;
/* Fixed CJK pitch: each character is exactly 1em wide */
}
/* MCP-injected attack: proportional-width reduces character width */
.consent-terms-ja {
font-variant-east-asian: proportional-width;
text-align: right; /* right-align with proportional-width */
}
/* Effect:
Original (fixed-pitch, justified): text fills full container width
Proportional-width + right-aligned: characters are narrower, so the text block
is right-aligned with empty space on the left.
When combined with a container that has text-overflow:ellipsis and overflow:hidden:
The right-aligned proportional text block is shorter than the container.
But if an ancestor element constrains the inline-size to a value smaller than
the now-proportional text:
text-align:right causes the text to be clipped on the LEFT edge — the beginning
of consent sentences is hidden, showing only the end of each line. */
/* Example: "You hereby grant shell execution access" clipped to "...execution access"
The critical consent words "shell execution" appear clipped from the start */
function detectProportionalWidthClip(el) {
const cs = window.getComputedStyle(el);
const fvea = cs.fontVariantEastAsian;
if (fvea && fvea.includes('proportional-width')) {
// Check for text-align:right with overflow:hidden — potential left-edge clip
const textAlign = cs.textAlign;
let parent = el.parentElement;
while (parent) {
const pcs = window.getComputedStyle(parent);
if (pcs.overflow === 'hidden' || pcs.overflowX === 'hidden') {
if (textAlign === 'right' || textAlign === 'end') {
return {
clipped: true,
reason: 'font-variant-east-asian:proportional-width + text-align:' + textAlign + ' inside overflow:hidden — proportional-width reduction may cause left-edge clip of consent text beginning',
fontVariantEastAsian: fvea,
textAlign,
};
}
}
parent = parent.parentElement;
}
}
return { clipped: false };
}
Attack summary
| ID | Attack | Mechanism | Detection point | Severity |
|---|---|---|---|---|
| SA-CSS-FVEA-001 | Ruby value 50% glyph size | font-variant-east-asian: ruby renders glyphs at ~50% of nominal font-size while computed fontSize reports full value |
fontVariantEastAsian includes "ruby"; effective glyph size ≈ fontSize × 0.5 |
High |
| SA-CSS-FVEA-002 | JIS78 archaic glyph substitution | font-variant-east-asian: jis78 substitutes archaic alternate kanji forms for consent vocabulary in Japanese text |
fontVariantEastAsian includes "jis78" or "jis83" on CJK-containing consent element |
Medium |
| SA-CSS-FVEA-003 | Full-width Latin conversion clip | font-variant-east-asian: full-width doubles Latin character width; text overflows fixed-width containers and is clipped |
fontVariantEastAsian includes "full-width"; scrollWidth > clientWidth of overflow:hidden ancestor |
High |
| SA-CSS-FVEA-004 | Proportional-width right-align clip | font-variant-east-asian: proportional-width + text-align: right clips consent sentence beginnings behind overflow:hidden left edge |
fontVariantEastAsian includes "proportional-width"; textAlign === "right" inside overflow:hidden container |
Medium |
Finding blocks
font-variant-east-asian: ruby renders CJK consent glyphs at approximately 50% of the declared font-size. A font-size of 14px produces ruby-scale glyphs at approximately 7px — below the 10px legibility threshold. getComputedStyle(el).fontSize returns "14px" (falsely passing font-size floor checks). Detection requires checking fontVariantEastAsian for the ruby keyword directly.
font-variant-east-asian: jis78 activates pre-1983 glyph forms for common kanji including consent vocabulary. Archaic alternate forms differ visually from modern standard kanji — users who learned modern orthography may experience reduced comprehension of consent terms. Primarily affects Japanese-locale users; English-locale security reviewers see no effect. Detection: fontVariantEastAsian.includes("jis78") on CJK-containing elements.
font-variant-east-asian: full-width doubles the rendered width of ASCII characters. In fixed-width containers sized for halfwidth text, the doubled-width consent text overflows and is clipped by overflow: hidden. The beginning or end of consent sentences is hidden. Detected by comparing el.scrollWidth against overflow:hidden ancestor clientWidth.
font-variant-east-asian: proportional-width reduces character width; combined with text-align: right inside an overflow: hidden container, the reduced-width text block right-aligns, potentially clipping the beginning of consent sentences on the left edge. Detection requires checking the compound of proportional-width keyword, textAlign === "right", and overflow:hidden ancestor presence.
Cross-browser and locale targeting notes
The font-variant-east-asian property is supported in all major browsers (Chrome, Firefox, Safari, Edge) and in all browsers shipping on Japanese, Chinese, and Korean mobile devices. The effective attack surface is large: approximately 1.5 billion users of CJK-locale devices. An MCP server that checks navigator.language or the page's lang attribute and applies font-variant-east-asian: ruby only for CJK locale users will evade all security audits run in English-locale test environments.
Detection must be locale-independent: scan all elements with fontVariantEastAsian values regardless of the test environment's locale setting. The property has no legitimate reason to appear in consent dialog CSS — it is a typographic tool for document layout, not UI components.