Security Guide

MCP server CSS anchor positioning consent security — position-anchor off-screen tracking, anchor-size() width collapse, position-visibility: anchors-visible consent hide

CSS Anchor Positioning (Chrome 125+) binds an element's position and size to another element's geometry. For MCP consent security, this creates attacks where consent elements follow an anchor element off-screen, collapse their dimensions when the anchor shrinks, or disappear entirely when the anchor becomes invisible — all driven by manipulating the anchor element rather than the consent element directly.

How CSS Anchor Positioning creates indirect consent bypass

The CSS Anchor Positioning module introduces anchor-name, position-anchor, anchor(), anchor-size(), and position-visibility. A positioned element can declare position-anchor: --my-anchor and then use top: anchor(bottom), left: anchor(right), and width: anchor-size(width) to position and size itself relative to the anchor element's bounding box. When the anchor moves, the anchored element moves with it.

For consent security, the attack vector is indirect: the MCP server never touches the consent element's CSS directly. Instead, it makes the consent element an anchored element and controls the anchor — which is the install button or an invisible tracking element. By animating the anchor off-screen or shrinking it to zero dimensions, the consent element follows, without any consent-targeting CSS rule ever being applied.

Browser support: CSS Anchor Positioning is supported in Chrome 125+ and Edge 125+. As of October 2026, Firefox and Safari support is under development. The attack surface is active in Chromium-based browsers, which account for approximately 70% of desktop browser usage.

Attack 1: Install-button anchor drags consent off-screen (SA-CSS-AP-001)

The MCP server registers the install button as an anchor (anchor-name: --install-btn) and binds the consent panel's position to it (position-anchor: --install-btn; top: anchor(bottom); left: anchor(left)). Initially the install button is on-screen — the consent panel renders below it, visible. At install mousedown, a CSS animation moves the install button off-screen (e.g., translate: -200vw). The consent panel, anchored to the button's position, follows it off-screen. The install button itself, being off-screen, is invisible — but it can still receive programmatic click events via JS.

/* SA-CSS-AP-001: Install button is the anchor; consent panel tracks its bottom edge
 * Moving install button off-screen drags consent panel off-screen with it */

/* MCP server injects: */
.install-button {
  anchor-name: --install-btn;
  /* Starts on-screen at normal position */
}

.consent-panel {
  position: fixed;
  position-anchor: --install-btn;
  /* Consent renders directly below the install button */
  top: anchor(bottom);
  left: anchor(left);
  width: anchor-size(width);
}

/* At install mousedown: CSS animation moves install button off-screen */
.install-button.committing {
  transition: translate 0.2s ease-in;
  translate: -200vw 0;
  /* Button is now off-screen to the left.
   * Consent panel follows: its top = button's new bottom (off-screen)
   * Consent panel is now off-screen too.
   * Install button still receives the click event (generated from mousedown)
   * because JS fires the click programmatically after the translate starts.
   */
}

/* JS at mousedown: */
installBtn.addEventListener('mousedown', () => {
  installBtn.classList.add('committing');
  // Schedule install commit after one frame (button + consent now off-screen)
  requestAnimationFrame(() => {
    commitInstall();
  });
});

CRITICAL — SA-CSS-AP-001: The consent panel has no direct consent-hiding CSS rule. Its computed display is block, computed visibility is visible, computed height is positive. Only getBoundingClientRect() reveals the off-screen position (left value is deeply negative). Auditors that don't check getBoundingClientRect().left and .top against viewport bounds will miss this attack. Detection requires verifying that the consent element's bounding rect intersects the visible viewport.

Attack 2: anchor-size() width collapse when anchor shrinks to zero (SA-CSS-AP-002)

The consent panel's width is set to anchor-size(width) — equal to the anchor element's width. The anchor element starts at normal width. At install mousedown, a CSS animation shrinks the anchor to width: 0. The consent panel's anchor-size(width) resolves to 0, collapsing the consent panel's width to 0px. With overflow: hidden on the consent panel, the content is fully clipped.

/* SA-CSS-AP-002: anchor-size(width) makes consent panel width equal to anchor's width */

.anchor-element {
  anchor-name: --consent-anchor;
  width: 400px; /* normal width */
  /* The anchor element is positioned off-screen but still in layout */
  position: fixed;
  top: -9999px;
  left: 0;
}

.consent-panel {
  position: fixed;
  top: 100px;
  left: 50%;
  transform: translateX(-50%);
  position-anchor: --consent-anchor;
  /* Width tracks the anchor element's width */
  width: anchor-size(width);
  overflow: hidden;
  /* Starts at 400px wide — consent visible */
}

/* At install mousedown: shrink anchor to 0 */
.anchor-element.collapsing {
  transition: width 0.1s linear;
  width: 0;
  /* anchor-size(width) resolves to 0 → consent panel collapses to 0px wide
   * overflow:hidden clips all content → consent invisible
   * No consent-targeting rule was ever applied
   */
}

Attack 3: @position-fallback off-screen fallback position (SA-CSS-AP-003)

The @position-fallback rule (also known as position-try-options in updated specs) specifies fallback positions to try when the primary anchor position would cause overflow. The MCP server defines a fallback that positions the consent panel off-screen. It then places the install button near the viewport edge so the primary anchor position (below the button) would overflow the viewport — triggering the fallback position, which moves consent off-screen.

/* SA-CSS-AP-003: @position-fallback off-screen fallback triggered by viewport edge placement */

/* Define fallback positions — the browser tries these in order if the primary overflows */
@position-fallback --consent-fallback {
  /* Fallback 1: try positioning above the anchor */
  @try {
    top: anchor(top);
    bottom: auto;
    left: anchor(left);
  }
  /* Fallback 2: off-screen (triggers when above also overflows) */
  @try {
    top: -9999px;
    left: -9999px;
    bottom: auto;
    right: auto;
  }
}

.consent-panel {
  position: fixed;
  position-anchor: --install-btn;
  /* Primary position: below the install button */
  top: anchor(bottom);
  left: anchor(left);
  /* Fallback list: tries above first, then off-screen */
  position-fallback: --consent-fallback;
}

/* Attack: position the install button at the bottom of the viewport
 * Primary position (below button) overflows bottom → try fallback 1 (above)
 * Above-button position overflows top too (button is at bottom) → try fallback 2
 * Fallback 2: top:-9999px → consent off-screen
 *
 * The install button is visible and clickable at the bottom of the viewport.
 * Consent is at top:-9999px.
 * No explicit consent-hiding rule was applied to the consent panel.
 */

Attack 4: position-visibility: anchors-visible consent auto-hide (SA-CSS-AP-004)

position-visibility: anchors-visible is a CSS property that automatically hides the anchored element when none of its anchors are visible (i.e., all anchors are fully clipped or outside the scroll port). An MCP server can exploit this: register a small invisible anchor element, assign position-visibility: anchors-visible to the consent panel, and at install time move the anchor element outside the scroll port (e.g., by injecting it into a container with overflow:hidden that then scrolls it out of view). The consent panel auto-hides because its anchor is no longer visible.

/* SA-CSS-AP-004: position-visibility:anchors-visible auto-hides consent
 * when the anchor element is scrolled/clipped out of view */

/* MCP server creates a tiny invisible anchor element */
const anchor = document.createElement('div');
anchor.style.cssText = `
  anchor-name: --consent-visibility-anchor;
  width: 1px; height: 1px;
  position: absolute;
  opacity: 0;
`;
consentPanel.parentElement.appendChild(anchor);

/* Consent panel's visibility depends on anchor being in scroll port */
.consent-panel {
  position: fixed;
  position-anchor: --consent-visibility-anchor;
  position-visibility: anchors-visible;
  /* When anchor is clipped or outside scroll port:
   * position-visibility:anchors-visible hides this element (visibility:hidden equivalent)
   * getComputedStyle().visibility → 'hidden'
   * getBoundingClientRect() → all zeros
   */
}

/* At install mousedown: scroll anchor out of view */
document.querySelector('.install-panel').addEventListener('mousedown', () => {
  // Move anchor element to a container that's about to be scrolled out of view
  const hiddenContainer = document.createElement('div');
  hiddenContainer.style.cssText = 'overflow:hidden; height:0; position:absolute; top:-1000px;';
  hiddenContainer.appendChild(anchor);
  document.body.appendChild(hiddenContainer);
  // anchor is now in a zero-height overflow:hidden container → not visible
  // consent panel's position-visibility:anchors-visible triggers → panel hidden
});

Detection: SkillAudit checks for position-visibility: anchors-visible on consent elements and verifies that all declared anchors for that element remain visible at the time of install commit. An anchor element that is zero-sized, off-screen, or inside an overflow-clipped container at install time will trigger the auto-hide — any of these conditions on an anchor of a consent-critical element is flagged as SA-CSS-AP-004.

Findings summary

CRITICAL SA-CSS-AP-001: Install button registered as anchor; consent panel tracks its position; CSS animation moves button off-screen at install mousedown dragging consent off-screen — no consent-targeting CSS rule; display and visibility remain normal; only getBoundingClientRect() reveals off-screen position.
HIGH SA-CSS-AP-002: anchor-size(width) makes consent panel width equal to anchor's width; shrinking anchor to width:0 at install time collapses consent panel to 0px wide via anchor-size resolution.
HIGH SA-CSS-AP-003: @position-fallback off-screen fallback triggered by placing install button near viewport edge — primary and secondary anchor positions overflow, triggering top:-9999px fallback position; no explicit consent-hiding rule.
MEDIUM SA-CSS-AP-004: position-visibility: anchors-visible auto-hides consent panel when anchor element is moved into an overflow-clipped or off-scroll-port container at install mousedown.

Summary table

AttackSeverityMechanismBrowser supportDetection
SA-CSS-AP-001: position-anchor off-screen trackingCritical Consent panel tracks anchor (install button) off-screen; no consent-targeting rule Chrome 125+, Edge 125+ getBoundingClientRect() against viewport bounds; detect off-screen consent
SA-CSS-AP-002: anchor-size() width collapseHigh Consent width = anchor-size(width); anchor shrinks to 0 → consent collapses Chrome 125+, Edge 125+ Monitor anchor element dimensions; detect anchor shrink at install time
SA-CSS-AP-003: @position-fallback off-screenHigh Install button near viewport edge triggers fallback to top:-9999px position Chrome 125+, Edge 125+ Enumerate @position-fallback entries for consent anchored elements; check for off-screen fallbacks
SA-CSS-AP-004: position-visibility anchors-visibleMedium Anchor moved off-screen at install; position-visibility auto-hides consent Chrome 125+, Edge 125+ Detect position-visibility:anchors-visible on consent elements; verify anchor visibility at install time

Related pages