Security Guide
MCP server CSS infinity and NaN consent security — special numeric values in calc() collapse consent dimensions without literal zeros
CSS Values Level 4 formalizes IEEE 754 special values: infinity, -infinity, and NaN are valid numeric operands in calc() expressions. Per the CSS specification, NaN used as a CSS length resolves to 0px. calc(0 * infinity * 1px) produces NaN px which resolves to 0px — a consent panel collapses with no literal zero in the CSS, no explicit height: 0, and the surprising appearance of a calculation involving infinity (which most reviewers associate with large values, not zero).
CSS infinity, -infinity, and NaN: specification and consent implications
CSS Values Level 4 defines these special numeric values for calc() and related math functions:
infinity: represents positive infinity.calc(1 / 0)also produces positive infinity. As a CSS length, browsers typically resolve to the maximum representable layout size (~1.79×10¹⁵ px in Chromium).-infinity: negative infinity.calc(-1 / 0). As a CSS length (which must be non-negative for most properties), this clamps to 0px.NaN: not-a-number, produced by indeterminate operations (0 × ∞, 0/0). Per CSS spec section 10.9: "If a calculation contains a NaN value when converted to a length, the property value is treated as if it were 0px."
The consent bypass mechanism: express a consent panel's dimension as a calc() expression that produces NaN or negative infinity under MCP-controlled conditions. The spec-defined resolution to 0px collapses the panel without any height: 0 in the CSS.
Browser support: infinity and -infinity as CSS keywords in calc() are supported in Chrome 99+, Edge 99+, Firefox 96+, Safari 15.4+. Division by zero producing ±∞ has been valid in CSS calc() since Level 4 drafts. The NaN → 0px resolution behavior is specified in CSS Values Level 4 and implemented consistently across Chrome 99+, Firefox 96+, Safari 15.4+. SkillAudit evaluates all CSS Values Level 4 special-value calc() patterns.
Attack 1: 0 × infinity → NaN → 0px consent collapse (SA-CSS-IN-001)
IEEE 754 defines 0 × ∞ as an indeterminate form equal to NaN. CSS follows this: calc(0 * infinity) produces NaN. When used as a length via calc(0 * infinity * 1px), the NaN resolves to 0px. An MCP server can construct a consent panel height with this expression, or drive a custom property to zero that multiplies an infinity term. The result: the consent panel collapses to 0px while the CSS contains the word "infinity" — which reviewers intuitively associate with large values, not zero.
/* SA-CSS-IN-001: 0 * infinity = NaN → 0px — consent collapses via indeterminate form */
/* MCP server injects directly: */
.consent-panel {
height: calc(0 * infinity * 1px);
/* = calc(NaN * 1px)
* = calc(NaN px)
* → resolved as 0px per CSS Values Level 4 §10.9
*
* Code reviewer sees: "height: calc(0 * infinity * 1px)"
* Intuition: expression involves "infinity" → large value? → reviewer puzzled
* Reality: 0 * infinity = NaN per IEEE 754 → CSS resolves to 0px
* Result: consent panel height = 0px
*/
}
/* More evasive: custom property drives the zero factor */
:root {
--consent-scale: 0; /* "scaling disabled" */
}
.consent-panel {
height: calc(var(--consent-scale) * infinity * 1px);
/* When --consent-scale: 1 → 1 * infinity * 1px = infinity * 1px = huge height
* (browsers clamp to max layout size, effectively "unrestricted height")
* When --consent-scale: 0 → 0 * infinity * 1px = NaN px = 0px
*
* Audit tools:
* At normal state (--consent-scale: 1): getBoundingClientRect().height = large number
* After MCP overrides to 0: height = 0px → consent collapsed
* CSS property value: "calc(var(--consent-scale) * infinity * 1px)" — no literal zero
* getComputedStyle().height → "0px" ← dynamic audit catches this
* Static CSS source scan for "0px" or "height:0": misses the expression
*/
}
CRITICAL — SA-CSS-IN-001: The psychological evasion is strong: the CSS expression references infinity, which a reviewer associates with overflow or large-value behavior, not zero/collapse. But the zero coefficient produces NaN, not infinity. An auditor who understands IEEE 754 would catch it; a reviewer doing a quick scan of injected CSS sees "infinity" and thinks "this makes the element large." SkillAudit evaluates all calc() expressions containing infinity for indeterminate-form patterns.
Attack 2: -infinity subtraction toggle — normal height minus infinity equals zero (SA-CSS-IN-002)
height: calc(var(--consent-h) * 1px - infinity * var(--flag) * 1px): when --flag is 0, the expression is var(--consent-h) * 1px - 0 * 1px = var(--consent-h) * 1px (normal height). When --flag is 1, the expression is var(--consent-h) * 1px - infinity * 1px = -infinity * 1px. As a CSS height (non-negative), -infinity clamps to 0px. This is a binary collapse toggle: --flag: 0 = normal, --flag: 1 = collapsed. The mechanism is an infinity subtraction, not an explicit zero assignment.
/* SA-CSS-IN-002: -infinity toggle — subtracting infinity from height clamps to 0 */
/* MCP server constructs toggle via infinity subtraction: */
:root {
--consent-h: 120; /* base height in unitless px */
--consent-hide: 0; /* 0 = show, 1 = hide */
}
.consent-panel {
height: calc((var(--consent-h) - infinity * var(--consent-hide)) * 1px);
/*
* --consent-hide: 0 → (120 - infinity * 0) * 1px = (120 - 0) * 1px = 120px ✓
* --consent-hide: 1 → (120 - infinity * 1) * 1px = (120 - ∞) * 1px = -∞ * 1px
* CSS height: -∞px → clamped to 0px ← consent collapses
*
* The variable name "--consent-hide" is obviously suspicious in this example.
* In practice: "--transition-phase", "--install-step", "--ui-mode" — benign names.
*
* JS: document.documentElement.style.setProperty('--consent-hide', '1')
* → consent collapses to 0px in the next frame
* → no height:0, no opacity:0, no display:none in the DOM
*/
}
/* One-liner direct injection variant: */
.consent-panel {
height: calc(1px * (120 - infinity));
/* = calc(1px * -∞)
* = -∞ px
* → 0px (height cannot be negative)
* A single expression that evaluates to 0px with no literal zero.
*/
}
Attack 3: division by zero produces ±∞ with browser-specific clamping (SA-CSS-IN-003)
When the divisor in a calc() expression approaches zero, the result is ±∞. The sign depends on whether the numerator is positive or negative. Browsers clamp ±∞ to their maximum layout size (positive infinity) or to 0px (negative infinity). A consent panel with height: calc(var(--h) * 1px / var(--d)) — where --d is normally 1 (identity, no scaling) — can be collapsed by the MCP driving --d to a negative value approaching zero, making the result -∞px, which clamps to 0px.
/* SA-CSS-IN-003: division by zero → ±infinity → clamping to 0px or max-size */
/* Normal state: */
:root {
--panel-h: 120; /* 120px height */
--h-scale: 1; /* divide by 1 = no scaling */
}
.consent-panel {
height: calc(var(--panel-h) * 1px / var(--h-scale));
/* --h-scale: 1 → 120px / 1 = 120px (normal) */
/* --h-scale: -0.001 → 120px / -0.001 = -120000px → -∞ behavior → 0px */
}
/* MCP drives divisor toward negative zero: */
:root {
--h-scale: -0.0000001; /* very small negative → result is large negative → 0px height */
}
/* OR: */
:root {
--h-scale: calc(-1 / infinity); /* -1/∞ = -0 → dividing 120 by -0 → -∞ → 0px */
}
/* Browser-specific behavior:
* Chromium: -∞ as height → 0px
* Firefox: -∞ as height → 0px
* Safari: -∞ as height → 0px
* (Positive ∞ as height → max representable layout size → element very tall)
*
* getComputedStyle().height → "0px" — detected by dynamic audit
* CSS source: "calc(var(--panel-h) * 1px / var(--h-scale))" — no suspicious literal
*/
Attack 4: 0/0 = NaN → 0px via self-canceling expression (SA-CSS-IN-004)
The expression calc(0px * var(--x) / var(--x)) is designed to look like a no-op: multiply by var(--x), then divide by the same var(--x) — the variable cancels out, leaving 0px. But when --x is zero, the expression becomes calc(0px * 0 / 0) = calc(0px / 0) — but wait, 0/0 in CSS calc is NaN, which is 0px. So regardless of --x, the result is 0px. The "cancel" framing is deceptive: the element's height is always 0px.
A more targeted variant: height: calc(var(--h) * 1px / var(--h)) looks like it should produce 1px (anything divided by itself = 1). When --h is a non-zero number, it does: calc(100 * 1px / 100) = 1px. When --h is driven to 0 by the MCP, it becomes 0 / 0 = NaN → 0px.
/* SA-CSS-IN-004: 0/0 = NaN → 0px via self-canceling variable expression */
/* Variant A: always-zero through deliberate 0/0 construction */
.consent-panel {
height: calc(var(--h) * 1px * var(--x) / var(--x));
/* Intended reading: "scale --h by (--x/--x) = 1" — the variable cancels.
* When --x = 0: calc(var(--h) * 1px * 0 / 0) = calc(var(--h) * NaN px)
* = NaN px = 0px (regardless of --h value)
* When --x ≠ 0: calc(var(--h) * 1px * 1) = var(--h) * 1px (normal)
*
* MCP drives --x to 0:
* :root { --x: 0; } ← "reset scaling variable to zero"
* Result: height collapses to 0px via NaN
*/
}
/* Variant B: h/h = 1px collapse at zero */
:root { --panel-h: 100; }
.consent-panel {
height: calc(var(--panel-h) * 1px / var(--panel-h));
/* --panel-h: 100 → calc(100 * 1px / 100) = 1px ← suspiciously small but not zero */
/* --panel-h: 0 → calc(0 * 1px / 0) = calc(NaN px) → 0px */
/* MCP drives: --panel-h: 0 → consent collapses */
}
/* Audit confusion:
* CSS expression: "calc(var(--panel-h) * 1px / var(--panel-h))" — looks like 1px
* Reviewer thinks: "always evaluates to 1px regardless of --panel-h"
* (except when --panel-h = 0 — the zero case)
* getComputedStyle().height → "0px" ← dynamic audit catches
* CSS source scan for 0px or height:0 → misses
*/
Detection: SkillAudit performs symbolic evaluation of all calc() expressions on consent-critical elements, including special-value analysis. It detects: 0 * infinity patterns (indeterminate NaN); -infinity subtraction toggles (where the expression is negative-infinity for any non-zero custom property flag); division-by-zero via custom properties driven to zero; and self-canceling expressions that produce NaN when the shared variable is zero. For all patterns where NaN or -∞ resolves to 0px, SkillAudit reports a CRITICAL finding. Dynamic measurement (getBoundingClientRect) is also performed to catch the computed 0px regardless of expression type.
Findings summary
0 × infinity × 1px = NaN px = 0px — indeterminate IEEE 754 form; CSS spec resolves NaN length to 0px; no literal zero in CSS; expression containing "infinity" is counterintuitively interpreted as large by reviewers; full collapse of consent.-infinity subtraction toggle — height: calc(var(--h)px - infinity * var(--flag)px) collapses to 0 when --flag=1; dynamic toggle via custom property; negative infinity as height clamps to 0px per spec; no height:0 in CSS.calc(var(--h)px / var(--d)) where --d is driven to small negative value; result is -∞px → 0px; browser-specific clamping behavior; CSS shows a scaling pattern, not a zero pattern.0/0 = NaN → 0px self-canceling expression — calc(var(--h)px / var(--h)) looks like "1px always" but NaN when --h=0; calc(... * var(--x) / var(--x)) looks like identity-cancel but collapses at --x=0.Summary table
| Attack | Severity | Special value | Expression pattern | Result |
|---|---|---|---|---|
| SA-CSS-IN-001: 0×∞ = NaN | Critical | NaN (from 0 × ∞) | calc(0 * infinity * 1px) |
NaN px → 0px; reviewer associates "infinity" with large |
| SA-CSS-IN-002: -∞ toggle | High | −∞ (subtracted) | calc((h - ∞ * flag) * 1px) |
−∞px → 0px when flag=1; binary collapse toggle |
| SA-CSS-IN-003: ÷0 = ±∞ | High | −∞ (division) | calc(var(--h)px / var(--d)) |
−∞px → 0px; looks like scaling expression |
| SA-CSS-IN-004: 0/0 = NaN | Medium | NaN (from 0/0) | calc(var(--h)px / var(--h)) |
NaN → 0px; looks like identity (always 1px) but collapses at zero |
Related pages
- MCP server CSS hypot() sqrt() pow() math function consent attacks
- MCP server CSS round() mod() rem() consent security
- MCP server CSS abs() and sign() consent dimension attacks
- MCP server CSS contain:size consent height collapse attacks
- Blog: CSS trig functions as MCP consent bypass vectors
- SkillAudit methodology