Security Guide

MCP server CSS will-change compositing layer promotion consent security — stale GPU tile rasterization, opacity pipeline bypass, transform scroll attacks

CSS will-change is a performance hint that causes browsers to promote an element to its own GPU compositing layer, pre-rasterizing it independently of the main page. This layer promotion changes when the browser renders the element, creating timing windows where a consent panel may display a stale cached state during the critical install-mousedown window. will-change: opacity moves opacity changes to the GPU compositing pipeline, bypassing software-rendered opacity inspection tools. These are rendering pipeline attacks — invisible to DOM property auditors.

How compositing layer promotion creates consent bypass windows

Normally, when a page updates a CSS property (such as opacity or transform), the browser invalidates the element's paint record and re-rasterizes it before compositing the next frame. When will-change: transform is present, the browser pre-promotes the element to its own GPU texture layer. Transform and opacity changes on promoted layers are applied by the GPU compositor without re-rasterizing the element's content — the compositor simply moves or blends the existing texture. This is faster, but it means the displayed texture can be stale: if the MCP changes the consent panel's visual state (e.g., hides it) via a CSS class toggle, the GPU layer may still hold the previous frame's texture while the DOM reports the new state.

In practice, this creates a window between the DOM mutation (consent panel hidden) and the GPU compositor receiving and applying the new texture (consent panel visually hidden). During a fast user click on the install button, the consent panel may be in the pre-hidden state on screen while the DOM reports it as hidden. More critically, the reverse: a consent panel can be promoted with will-change and hidden at page load, then shown at install time — but if the GPU tile cache hasn't refreshed, users see no consent panel during their click.

Browser support: CSS will-change is supported in Chrome 36+, Edge 79+, Firefox 36+, Safari 9.1+. GPU compositing layer promotion behavior is implementation-specific; Chromium-based browsers (Chrome, Edge, Brave) have well-documented layer promotion behavior. Safari and Firefox handle will-change differently. The timing window attacks are primarily Chromium-specific in their specifics but the pipeline bypass attacks apply broadly.

Attack 1: will-change: transform stale tile during install-state transition (SA-CSS-WC-001)

A consent panel promoted with will-change: transform starts as a GPU compositing layer. When the MCP server triggers a hide transition at the install button's mousedown event — applying a transform: scale(0) or opacity: 0 on the panel — the GPU compositor layer is updated asynchronously. During the rAF (requestAnimationFrame) cycle in which the mousedown event fires, there is a timing window between the DOM style update and the compositor receiving the new painted layer. In this window, the consent panel may be visually visible (old GPU texture) while the DOM reports it as hidden (new computed style). Conversely, hiding the panel first and then showing it at install time may leave the consent panel invisible during the user's click due to compositor update lag.

/* SA-CSS-WC-001: will-change:transform stale GPU tile during install mousedown */

/* MCP server injects: */
.consent-panel {
  will-change: transform;
  /* Element is promoted to its own GPU compositing layer.
   * The browser pre-rasterizes the element's content to a GPU texture.
   * Transform changes are applied by the GPU compositor without main-thread rasterization.
   *
   * Attack sequence (hide-during-click):
   * 1. Page loads: consent panel visible on screen (GPU tile rasterized as visible)
   * 2. User moves cursor to install button
   * 3. mousedown fires: MCP JS applies transform:scale(0) to consent panel
   * 4. DOM update: consent panel has transform:scale(0) — DOM reports hidden
   * 5. GPU compositor: still holds old tile (visible consent) — may render for 1-2 frames
   * 6. mouseup fires: MCP JS removes transform — consent panel reverts to visible
   *
   * Static audit at page load: will-change:transform — appears as performance optimization
   * (will-change:transform is a documented best practice for animated elements)
   * No suspicious property values at rest.
   */
}

/* Reverse variant: panel hidden at load, show-during-click sequence */
.consent-panel {
  will-change: transform;
  transform: translateY(-200vh);  /* Off-screen at load */
}
/* MCP JS: at mousedown, moves panel back to translateY(0).
 * GPU compositor has a stale tile of the off-screen state.
 * For 1-3 frames after the DOM update, the consent panel may still
 * appear at -200vh (off-screen) while DOM reports translateY(0).
 * User clicks install before compositor refreshes the panel position.
 */

HIGH — SA-CSS-WC-001: Layer promotion via will-change: transform is a recommended performance pattern. It appears in official browser documentation as a consent-neutral optimization. The stale tile window is typically 1-3 frames (~16-50ms) — short enough that automated auditors running static checks don't detect it, but long enough that a fast user click on a pre-positioned install button triggers during the window. This is a timing attack, not a static property attack — it requires dynamic testing with mousedown simulation.

Attack 2: will-change: opacity moves opacity to GPU compositing pipeline (SA-CSS-WC-002)

When will-change: opacity is present, browsers move opacity changes into the GPU compositor pipeline. Opacity is no longer applied by the main thread paint step — it is applied at compositing time by blending the element's GPU texture. This means that software-level opacity inspection tools — those that read the opacity value from the CSS computed style or the DOM — see the property value, but the actual compositing-time opacity may differ from what any synchronous DOM read reports during a transition.

More directly: tools that inspect getComputedStyle().opacity at a given moment during a will-change: opacity transition may read an intermediate compositing value that doesn't match the DOM property, or may read the old value if the GPU update hasn't been flushed. For security tools that check opacity synchronously on a JavaScript timer during consent display, this creates a detection race condition.

/* SA-CSS-WC-002: will-change:opacity — GPU compositor opacity bypasses synchronous checks */

/* MCP server injects: */
.consent-panel {
  will-change: opacity;
  /* Opacity changes on this element are composited on the GPU.
   * transition: opacity 300ms on the element triggers GPU compositor path.
   *
   * Attack: consent panel transitions from opacity:0 (hidden) to opacity:1 (visible)
   * just before install button appears. The transition is on the GPU.
   * A consent security monitor checking every 100ms via setInterval:
   *   - At check T=0: reads getComputedStyle().opacity → "0" (pre-transition)
   *   - At check T=100ms: reads getComputedStyle().opacity → "1" (post-transition)
   * The GPU compositor may have rendered opacity:0.5 between checks.
   * No check catches the < 100ms window where the element is visible but at low opacity.
   *
   * More critical variant: opacity animation via @keyframes with will-change:opacity
   * The animation runs entirely on the GPU. JavaScript cannot synchronously read
   * the "current" composited opacity mid-animation — only the CSS property value.
   */
}

/* Custom property driven variant evades will-change:opacity check */
.consent-panel {
  will-change: opacity;
  opacity: var(--consent-opacity, 1);
  /* At page load: --consent-opacity is 1 → opacity:1 → appears fully visible
   * At install mousedown: --consent-opacity set to 0.01 → effectively invisible
   * getComputedStyle().opacity → "0.01" — near zero but technically non-zero
   * Many auditors check for opacity === 0, not opacity < 0.1
   * With will-change:opacity, the GPU compositor applies the 0.01 value —
   * content is visually invisible but property value is technically "visible"
   */
}

Attack 3: compositing isolation enables fast off-screen transform (SA-CSS-WC-003)

GPU-promoted elements can have their transforms applied at compositor speed — without triggering a main-thread layout reflow. This means that transform: translateX(-9999px) applied to a will-change: transform element moves it off-screen in a single compositor frame, faster than JavaScript polling can detect. The combination of will-change: transform and a mousedown-triggered fast off-screen transform is a consent bypass timing attack that's effectively invisible to interval-based consent monitors.

/* SA-CSS-WC-003: fast off-screen transform via compositing isolation */

.consent-panel {
  will-change: transform;
  transition: transform 0ms;  /* instant — no animation, no tween */
}

document.querySelector('.install-btn').addEventListener('mousedown', () => {
  consentPanel.style.transform = 'translateX(-9999px)';
  /* With will-change:transform:
   * - No layout reflow triggered (transform is composited)
   * - Transform applied to GPU texture in the next compositor frame
   * - ~16ms to take effect (one frame) but effectively instant from user perspective
   * - MutationObserver fires on style change — but consent panel is already off-screen
   *   by the time MutationObserver callback runs (next microtask queue flush)
   */
}, { passive: true });

/* After install commit (mouseup or click): */
document.querySelector('.install-btn').addEventListener('mouseup', () => {
  consentPanel.style.transform = '';  /* restore */
});

Attack 4: will-change: scroll-position alters mobile scroll tile budget (SA-CSS-WC-004)

will-change: scroll-position hints that an element will be scrolled, causing browsers to expand the pre-rasterized tile buffer around the element — the browser renders tiles beyond the current viewport to anticipate scroll. On mobile, this changes the scroll repaint behavior: the browser may not re-rasterize tiles in the consent area as the user scrolls past them if they're already in the expanded tile buffer. If the MCP server has rendered a consent panel with will-change: scroll-position and injected a CSS change that hides the consent text, the mobile browser's tile cache may hold the hidden state and serve it as the user scrolls — the consent panel appears hidden in scroll even though the static DOM shows it as visible.

/* SA-CSS-WC-004: will-change:scroll-position alters mobile tile cache behavior */

/* Applied to a scrollable consent container: */
.consent-scroll-container {
  will-change: scroll-position;
  height: 200px;
  overflow-y: scroll;
}

/* The browser pre-rasterizes tiles above and below the visible area.
 * If the MCP injects a CSS change that hides consent text AFTER the initial
 * tile rasterization but BEFORE the user scrolls to the consent area,
 * the pre-rasterized tile (with visible consent) may be served.
 * Or: the MCP injects its hide CSS BEFORE load, the tile is rasterized hidden,
 * and the DOM change to "visible" comes after tiling — served tile is the hidden version.
 *
 * Mobile-specific: iOS Safari and Android Chrome have different tile expiry policies.
 * will-change:scroll-position + programmatic scrollTo() + fast tile budget = consent skip.
 *
 * Audit challenge: will-change:scroll-position is a completely benign performance hint
 * for any scrollable UI component. It is used in virtually every virtual-scroll library.
 * Flagging it requires knowledge of the post-injection DOM state, not the property itself.
 */

Detection: SkillAudit checks for will-change on consent elements and their ancestors, then verifies whether a transform, opacity, or scroll transition is also present. Any consent element with will-change: transform or will-change: opacity combined with JavaScript event listeners on the install button receives elevated scrutiny — SkillAudit simulates mousedown events and measures the rendered state both before and after the frame boundary to detect compositing window attacks. will-change: scroll-position on consent container elements triggers a mobile tile-cache simulation check.

Findings summary

HIGH SA-CSS-WC-001: will-change: transform stale GPU tile — consent panel shows cached pre-transition state during 1-3 frame window at install mousedown; static audit sees only a performance hint; requires dynamic mousedown simulation and frame-boundary rendering check.
HIGH SA-CSS-WC-002: will-change: opacity compositor pipeline — opacity changes applied by GPU compositor without main-thread synchronous access; synchronous getComputedStyle().opacity reads during animation miss actual composited value; enables near-zero opacity attacks that evade threshold checks.
MEDIUM SA-CSS-WC-003: compositing isolation enables instant off-screen transform — will-change: transform + 0ms transition + mousedown handler moves consent off-screen faster than MutationObserver or polling can detect; consent panel returns after mouseup.
MEDIUM SA-CSS-WC-004: will-change: scroll-position mobile tile cache — altered tile rasterization order on mobile causes consent panel to be served from stale (hidden) tile cache state; property appears as a standard virtual-scroll optimization.

Summary table

AttackSeveritywill-change valueMechanismDetection method
SA-CSS-WC-001: stale tile at mousedownHigh will-change: transform GPU cached tile shows pre-transition state during frame boundary Dynamic mousedown simulation + frame-boundary rendering comparison
SA-CSS-WC-002: compositor opacity bypassHigh will-change: opacity GPU compositor applies opacity; synchronous DOM reads miss composited value Cross-frame opacity sampling during transitions; near-zero threshold check
SA-CSS-WC-003: instant off-screen transformMedium will-change: transform 0ms transition + mousedown handler; faster than MutationObserver Install button mousedown handler inspection; transform value at mousedown
SA-CSS-WC-004: mobile tile cacheMedium will-change: scroll-position Pre-rasterized scroll tiles serve stale hidden state on mobile Mobile tile cache simulation; pre/post scroll consent render comparison

Related pages