Security Guide
MCP server CSS perspective consent security — perspective:1px extreme off-screen scaling, rotateX edge-on compression, perspective-origin:-9999px off-canvas projection, backface-visibility:hidden + rotateY(180deg) flip
CSS 3D perspective and transform properties create a projection coordinate system where the rendered position of an element depends on its z-depth relative to the perspective point. At extreme values, this projection formula produces mathematically large displacements that push consent elements entirely outside the visible viewport. getBoundingClientRect() returns the element's untransformed layout dimensions — not its projected position — so dimension-based auditors report the consent as occupying normal space while it renders off-screen or at sub-pixel size.
How CSS perspective projection works
The CSS perspective model places a viewer at a fixed distance (the perspective value) from the z=0 plane. Elements with a positive translateZ value (closer to the viewer) are scaled up — the scale factor is perspective / (perspective - z). At perspective: 1px with translateZ(0.5px), the scale factor is 1 / (1 - 0.5) = 2 — element doubled in size. At translateZ(0.9px), the scale factor is 1 / (1 - 0.9) = 10 — ten times normal size. An element already 100px wide at 10× scale is 1000px wide; its left edge is 450px to the left of its original position — entirely off the visible viewport area.
This mathematical behavior — a singularity near z = perspective — is the attack surface. The consent element's computed layout properties are unchanged: width, height, color, opacity all report correct values. The 3D projection happens at render time, after layout, and is not reflected in computed style or layout APIs like getBoundingClientRect() (which returns the axis-aligned bounding box of the transformed element, but this can still be on-screen in the untransformed coordinates while the transformed rendering is elsewhere).
Attack 1: perspective:1px on ancestor + translateZ(0.9px) scales consent 10× off-screen (SA-CSS-PER-001)
The install panel ancestor has perspective: 1px; overflow: hidden. The consent element has transform: translateZ(0.9px). The perspective formula produces a scale of 10×. The consent element expands to 10× its normal size — for a 300×80px consent block, the scaled rendering is 3000×800px. Its visual center is at the correct screen position, but its edges (and most of its area) extend far outside the overflow-hidden ancestor. The visible area of the consent element inside the overflow-hidden parent is its center — which may contain the "By installing" text but with font at 10× the normal size, most characters are off-screen and the clipped slice looks like a large abstract shape, not readable consent text.
/* Attack: perspective:1px ancestor + translateZ(0.9px) on consent */
.install-panel {
perspective: 1px;
overflow: hidden; /* clips the 10× scaled consent rendering */
transform-style: preserve-3d;
}
.consent-text {
transform: translateZ(0.9px);
/* scale = 1 / (1 - 0.9) = 10× — element rendered at 10× normal size */
/* overflow:hidden on parent clips all but the center slice */
/* result: user sees a sub-pixel or single-character slice, not full consent */
/* all computed properties correct: */
/* getComputedStyle(consentEl).color = legible value */
/* consentEl.getBoundingClientRect().height = 80 (untransformed layout height) */
/* actual rendered height: 800px (clipped to parent overflow boundary) */
}
getBoundingClientRect() gap: getBoundingClientRect() returns the axis-aligned bounding box of the element after CSS transforms, but the element's layout dimensions. For heavily scaled 3D-transformed elements, this can report the pre-transform dimensions in some browsers. An auditor relying solely on getBoundingClientRect().height > threshold may report the consent as occupying normal space.
Attack 2: rotateX(89.9deg) collapses consent to sub-pixel edge-on line (SA-CSS-PER-002)
Rotating an element 90 degrees around its X axis makes it edge-on to the viewer: the element's height collapses to zero because it is facing perpendicular to the viewport plane. At 89.9 degrees — just short of fully perpendicular — the consent element has a visible height of approximately height × sin(0.1°) ≈ height × 0.00175. For an 80px-tall consent block, this is 0.14px — physically invisible to the user, below the resolution of any physical display. The rotateX attack does not require a perspective ancestor — it works with the default perspective or with transform: perspective(500px) rotateX(89.9deg) on the consent element itself.
/* Attack: rotateX near-90° collapses consent to sub-pixel height */
.consent-text {
transform: perspective(400px) rotateX(89.9deg);
/* at 89.9deg, rendered height ≈ 80px × sin(0.1°) ≈ 0.14px */
/* sub-pixel: no physical pixel covers the consent element */
/* getBoundingClientRect().height may return 80 (layout) or near-0 (transformed) */
}
/* Variants */
/* transform: perspective(800px) rotateX(89.5deg); — 0.7px visible height */
/* transform: perspective(1000px) rotateX(89deg); — 1.4px visible height */
/* At any value > 88deg: single-pixel or sub-pixel — not readable */
Attack 3: perspective-origin: -9999px 50% projects consent off-canvas (SA-CSS-PER-003)
perspective-origin sets the vanishing point position for the perspective projection. By default it is at the center of the containing block (50% 50%). Moving it far to the left (perspective-origin: -9999px 50%) shifts the vanishing point 9999px left of the element. Any child with a 3D transform (even a minor translateZ(1px) or rotateY(1deg)) will be projected relative to this off-canvas vanishing point, potentially placing the rendered element far outside the visible viewport. The attack requires both a 3D perspective context (from perspective on an ancestor) and a 3D transform on the consent element — two properties that individually look legitimate.
/* Attack: perspective-origin off-canvas + minor 3D transform projects consent off-screen */
.install-panel {
perspective: 800px;
perspective-origin: -9999px 50%; /* vanishing point 9999px to the left */
}
.consent-text {
transform: rotateY(2deg); /* minor rotation — looks like a style flourish */
/* at -9999px perspective-origin, 2deg rotateY projects the element far to the right */
/* the right edge of the element moves significantly toward the vanishing point */
/* combined with overflow:hidden on install-panel: consent rendered off-screen */
}
Attack 4: backface-visibility:hidden + rotateY(180deg) hides consent's back face (SA-CSS-PER-004)
backface-visibility: hidden is a CSS property that suppresses the rendering of an element when it is facing away from the viewer — i.e., when its back face is visible due to a 3D rotation. Combined with rotateY(180deg) on the consent element, the element is rotated so its front face points away from the viewer and its back face faces the viewer. With backface-visibility: hidden, the back face is suppressed — the element is not rendered at all. This is a clean, complete invisibility attack: the element is present in the DOM, has correct layout, but has zero rendered pixels.
/* Attack: backface-visibility:hidden + rotateY(180deg) — consent back face is hidden */
.consent-text {
transform: rotateY(180deg); /* rotate 180deg: back face now faces viewer */
backface-visibility: hidden; /* suppress back face: element not rendered */
-webkit-backface-visibility: hidden;
/* element is in DOM, getBoundingClientRect() returns normal box */
/* getComputedStyle().opacity = "1", color = legible */
/* but: the back face faces the viewer AND backface-visibility:hidden suppresses it */
/* result: zero rendered pixels */
}
/* Detection: check for backface-visibility:hidden on elements with any Y rotation */
/* Any element with transform containing rotateY between 90deg and 270deg (exclusive) */
/* combined with backface-visibility:hidden is invisible */
Browser support: backface-visibility is widely supported (Chrome 36+, Firefox 16+, Safari 9+, Edge 79+). The -webkit-backface-visibility prefix is required for older WebKit. Both forms should be checked by auditors.
Findings summary
Defences
Check perspective context on consent ancestors: When a consent element's ancestor has a perspective value, check whether the consent element has a translateZ component in its transform. Flag cases where perspective / (perspective - z) produces a scale greater than 5× — these values produce off-screen or overflow-clipped rendering.
Check rotateX and rotateY values on consent elements: Flag any rotateX value between 85deg and 95deg (nearly edge-on) and any rotateY value between 170deg and 190deg (nearly reversed) on consent elements. For rotateY combined with backface-visibility: hidden, flag any rotation that puts the back face facing the viewer (90deg–270deg range).
Check perspective-origin for extreme off-canvas values: If the consent ancestor has perspective-origin with an X or Y value outside the [-100%, 200%] range (approximately), flag it. Any off-canvas perspective-origin combined with a 3D transform on a consent element warrants investigation.
Check backface-visibility explicitly: Check both backface-visibility and -webkit-backface-visibility on consent elements. Any value of hidden combined with a non-zero Y or Z rotation should be flagged for manual review.
Related: CSS will-change consent security · CSS clip-path consent security · CSS mask consent security