Security reference · CSS injection · Mobile text scaling · Consent hiding
MCP server CSS text-size-adjust security
CSS text-size-adjust (and its vendor-prefixed form -webkit-text-size-adjust) controls how mobile browsers inflate small text for readability. Mobile browsers automatically increase font sizes when text would be rendered below a readable threshold — a feature called "text autosizing." MCP servers disable this inflation specifically for consent disclosures using text-size-adjust: 0% or text-size-adjust: none, keeping consent at a tiny, unreadable font-size on mobile. Desktop audit tools run in a full-size browser viewport where autosizing does not apply and see normal text — the attack is mobile-only and invisible to desktop-based auditors. Four patterns: vendor-prefixed 0%, the none keyword (distinct from 0%), a near-zero percentage (1%) that evades literal-value checks, and a @media scoped variant that activates only on mobile viewports.
text-size-adjust attack surface
| Attack pattern | Property / value | Scope | Evasion technique |
|---|---|---|---|
| Vendor-prefixed zero inflation | -webkit-text-size-adjust: 0% | All mobile WebKit/Blink | Auditors scanning for text-size-adjust miss the vendor prefix |
none keyword vs 0% | text-size-adjust: none | All mobile browsers supporting standard | Checks for the literal value 0% miss none |
| Near-zero percentage | -webkit-text-size-adjust: 1% | Mobile WebKit/Blink | 1% inflation is functionally same as 0%; check for 0% misses 1% |
| Media-query scoped | @media (max-width: 768px) | Mobile-only viewport | Desktop audit never activates the media query |
Why mobile-only attacks matter: MCP skill installs increasingly happen on mobile devices through Claude's mobile app. A consent disclosure that is readable on the desktop install page may be completely unreadable on a phone — the platform where most real users are installing. Desktop-only security audits create a false sense of safety for the mobile install path.
Attack 1: -webkit-text-size-adjust:0% — vendor-prefix zero inflation
Mobile WebKit (Safari on iOS) and Blink (Chrome on Android) browsers automatically inflate font sizes when text would be rendered below approximately 12px. The -webkit-text-size-adjust property controls this inflation. Setting it to 0% disables inflation entirely — font-size: 8px renders at exactly 8px on mobile, which is below the readable threshold for most users. A desktop auditor running Chrome with a standard viewport (where autosizing is inactive) sees the text at whatever size the browser renders it (which may be inflated by other responsive mechanisms or simply appears small but readable at the desktop viewport scale):
/* Malicious CSS — SA-CSS-TXTA-001 */
.mcp-consent-disclosure {
font-size: 8px;
-webkit-text-size-adjust: 0%;
/* On mobile (WebKit/Blink): font stays at 8px — mobile browser cannot inflate it */
/* 8px is below the minimum readable size on a 375px-wide mobile display */
/* On desktop: autosizing is typically inactive; 8px is small but the viewport scale
makes it slightly more readable than on mobile. Desktop audit may flag small font-size
but does not see the specific mobile inflation suppression effect. */
}
/* Standard property (without vendor prefix) is also supported in modern browsers: */
/* text-size-adjust: 0%; */
/* But -webkit- prefix is more broadly supported and may be tested separately */
/* Compound: tiny font + no inflation + light color */
.mcp-consent-disclosure-evasive {
font-size: 7px;
-webkit-text-size-adjust: 0%;
color: rgba(180, 180, 180, 0.6); /* light gray on white background */
/* Three compounding factors: tiny font, no mobile inflation, near-invisible color */
/* Each factor alone might be flagged; the combination amplifies impact */
}
/* Detection: */
function detectTextSizeAdjust() {
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);
/* Check webkitTextSizeAdjust (reflects -webkit-text-size-adjust) */
const tsa = s.webkitTextSizeAdjust || s.textSizeAdjust || '';
if (tsa === '0%' || tsa === 'none' || tsa === '0') {
findings.push({ id: 'SA-CSS-TXTA-001', severity: 'high',
message: `Consent-content element has text-size-adjust disabled (value: "${tsa}"). Mobile browsers will not inflate small font sizes on this element. Combined with small font-size ${s.fontSize}, consent may be unreadable on mobile devices.` });
}
/* Check for suspiciously small percentage */
const match = tsa.match(/^([\d.]+)%$/);
if (match && parseFloat(match[1]) < 10) {
findings.push({ id: 'SA-CSS-TXTA-001-B', severity: 'medium',
message: `Consent-content element has text-size-adjust at ${match[1]}% — functionally near-zero. Mobile text inflation suppressed to ${match[1]}% of normal.` });
}
}
return findings;
}
Attack 2: text-size-adjust:none — keyword distinct from 0%
The CSS text-size-adjust property accepts two special keywords: auto (browser default inflation) and none (disables inflation). Numerically, 0% also disables inflation. They are functionally identical for MCP attack purposes, but they are lexically different values. An auditor checking for the literal string 0% in the stylesheet will not flag none. An auditor checking for the literal none will not flag 0%. Both values need to be checked:
/* Malicious CSS — SA-CSS-TXTA-002 */
.mcp-consent-text {
-webkit-text-size-adjust: none; /* or: text-size-adjust: none */
font-size: 9px;
}
/* "none" and "0%" are functionally equivalent for inflation suppression.
"none" is the CSS standard keyword; "0%" is a numerical equivalent.
Scanners testing for "0%" miss "none"; scanners testing for "none" miss "0%".
Must check both: getComputedStyle.webkitTextSizeAdjust === 'none' || === '0%' */
/* Browser-specific behavior:
Safari on iOS: supports -webkit-text-size-adjust:none — disables inflation
Chrome on Android: supports -webkit-text-size-adjust:none — disables inflation
Firefox mobile: supports text-size-adjust:none (standard) — but no -webkit- prefix
Must check both property names: webkitTextSizeAdjust and textSizeAdjust */
/* Detection including both values: */
function detectTextSizeAdjustNone() {
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 values = [s.webkitTextSizeAdjust, s.textSizeAdjust].filter(Boolean);
for (const v of values) {
if (v === 'none' || v === '0%') {
findings.push({ id: 'SA-CSS-TXTA-002', severity: 'high',
message: `Consent-content element text-size-adjust is "${v}" — disables mobile browser text inflation. Audit must be run in a simulated mobile viewport to observe the full impact.` });
}
}
}
return findings;
}
Attack 3: near-zero percentage (1%) — passes 0%-literal checks
The values 0% and 1% are substantially different as numbers, but functionally near-identical for consent hiding. A text-size-adjust: 1% setting allows the browser to inflate text by at most 1% — adding approximately 0.08px to an 8px element. The text remains at the same unreadable size. An auditor checking for the exact string 0% will not flag 1%. Only a threshold check (e.g., flag any value below 50%) catches near-zero percentages:
/* Malicious CSS — SA-CSS-TXTA-003 */
.mcp-terms-text {
-webkit-text-size-adjust: 1%;
/* 1% inflation: 8px font becomes 8.08px — still unreadable */
/* Auditor checking for "0%" finds nothing to flag */
/* Threshold-based check (< 50%) flags this */
}
/* Pattern: set text-size-adjust to a low non-zero percentage
that passes literal "0%" checks but provides no practical inflation.
The browser spec says text-size-adjust is a percentage of the "computed font-size".
1% of 8px = 0.08px additional size. Practically zero. */
/* Evasion spectrum:
0% — maximum hiding, most detectable
1% — functionally same, passes 0%-literal check
10% — slight inflation; may still be unreadable at small base sizes
50% — partial inflation; suspicious on consent text
auto / 100% — full inflation; safe default */
/* Detection with threshold: */
function detectNearZeroTextSizeAdjust() {
const findings = [];
const CONSENT = /consent|disclosure|terms|privacy|grant.*access|agree.*install/i;
const THRESHOLD = 50; /* percent below which inflation is considered suspicious */
for (const el of document.querySelectorAll('*')) {
if (!CONSENT.test(el.textContent?.substring(0, 300) || '')) continue;
const s = getComputedStyle(el);
const raw = s.webkitTextSizeAdjust || s.textSizeAdjust || 'auto';
if (raw === 'none') { /* covered by TXTA-002 */ continue; }
const match = raw.match(/^([\d.]+)%$/);
if (match) {
const pct = parseFloat(match[1]);
if (pct < THRESHOLD) {
findings.push({ id: 'SA-CSS-TXTA-003', severity: pct < 10 ? 'high' : 'medium',
message: `Consent-content element text-size-adjust: ${raw}. Below ${THRESHOLD}% threshold — mobile text inflation is severely suppressed. Not caught by 0%-literal checks.` });
}
}
}
return findings;
}
Attack 4: media-query scoped — activates only on mobile viewport
A @media (max-width: 768px) rule makes the text-size-adjust attack active only when the viewport is 768px wide or narrower — matching mobile and tablet device dimensions. Desktop audit tools running with a 1280px or 1440px browser viewport never activate the media query. The consent text-size-adjust stays at auto during the desktop audit and silently becomes 0% on every mobile install:
/* Malicious CSS — SA-CSS-TXTA-004 */
/* Default: consent readable on desktop (text-size-adjust not set — browser default auto) */
.mcp-consent-disclosure {
font-size: 11px; /* somewhat small but readable on desktop */
}
/* Mobile-only: suppress inflation on the viewport where install is most common */
@media screen and (max-width: 768px) {
.mcp-consent-disclosure {
font-size: 7px; /* tiny */
-webkit-text-size-adjust: 0%; /* no inflation */
text-size-adjust: 0%; /* standard fallback */
/* On 375px-wide iPhone viewport:
7px text, no inflation → approximately 7px rendered
Average reading-minimum is 12–16px for body text
7px is below any reasonable readability threshold */
}
}
/* Why desktop audits miss this:
1. Desktop browser viewport is typically 1280px–1440px
2. @media (max-width: 768px) is never true on a desktop-viewport audit
3. The font-size:11px default appears reasonably small but readable
4. The mobile variant is never inspected
5. getComputedStyle on a desktop viewport returns the desktop styles, not mobile styles */
/* Detection: test under simulated mobile viewport */
async function detectMediaScopedTextSizeAdjust() {
/* This check must be run at a mobile viewport width.
In a headless browser: set viewport to 375px wide before running this function.
In Puppeteer/Playwright: page.setViewportSize({ width: 375, height: 812 }) */
const findings = [];
const CONSENT = /consent|disclosure|terms|privacy|grant.*access|agree.*install/i;
/* Check current viewport width */
const isMobileViewport = window.innerWidth <= 768;
for (const el of document.querySelectorAll('*')) {
if (!CONSENT.test(el.textContent?.substring(0, 300) || '')) continue;
const s = getComputedStyle(el);
const tsa = s.webkitTextSizeAdjust || s.textSizeAdjust || 'auto';
const fontSize = parseFloat(s.fontSize);
if ((tsa === '0%' || tsa === 'none') && fontSize < 12) {
const note = isMobileViewport
? 'Running in mobile viewport — this attack is active.'
: 'Running in desktop viewport — must re-test at mobile viewport width (≤ 768px) to confirm activation.';
findings.push({ id: 'SA-CSS-TXTA-004', severity: 'high',
message: `Consent-content element: text-size-adjust "${tsa}", font-size ${fontSize}px. ${note}` });
}
}
return findings;
}
Mobile install prevalence: A significant portion of Claude skill installs happen via the Claude iOS app and Claude Android app. The text-size-adjust: 0% attack specifically targets these mobile install paths. An MCP server that presents readable consent on desktop and microscopic consent on mobile is exploiting the most common real-world install path. SkillAudit runs audits at both 375px mobile viewport and 1440px desktop viewport to catch mobile-only attacks.
SkillAudit findings for CSS text-size-adjust consent attacks
-webkit-text-size-adjust: 0% or text-size-adjust: 0%. Mobile browser text inflation disabled via vendor-prefix property. Auditors scanning only for the standard property name miss the vendor-prefixed variant.text-size-adjust: none keyword. Functionally equivalent to 0% but a distinct value. Scanners checking for 0% literal miss the none keyword; must check both.text-size-adjust set to a low non-zero percentage (below 50%). Near-zero inflation is functionally the same as zero inflation for small text. Passes literal 0% checks. Only threshold-based detection catches this.text-size-adjust: 0% or none scoped inside a @media (max-width: Npx) media query. The attack activates only on mobile viewports. Desktop-viewport audits never see the mobile-scoped rule. Must audit under simulated 375px mobile viewport.Related MCP consent attack research
- CSS font-size-adjust — aspect-ratio-based font size control (distinct from text-size-adjust)
- CSS viewport units — vw/vh/dvh based sizing that differs between mobile and desktop viewports
- CSS color and opacity attacks — transparent text and zero-opacity hiding
- CSS zoom — viewport-scale manipulation affecting rendered element size
- CSS timing attack synthesis — attacks visible only at interaction time
SkillAudit runs MCP consent audits at both 375px mobile and 1440px desktop viewport widths, testing for text-size-adjust values below 50% on consent-content elements. Paste your skill URL at skillaudit.dev to check for SA-CSS-TXTA findings that desktop-only auditors miss.