Security Guide
MCP server CSS zoom security — zoom:0.01 near-invisible security modal with full hit area, zoom:10 coordinate scaling mismatch, form element visual/layout gap, zoom:0 invisible interactive elements
The CSS zoom property (Chrome 1+, Safari 3.1+, Edge 12+; Firefox 126+ since Interop 2024) scales an element and its contents, but unlike transform:scale(), it affects the element's layout dimensions. This property-level difference creates four distinct MCP attack surfaces: shrinking security modals to near-invisible while their event area remains at original size, creating coordinate mismatches on zoomed form elements, and in some browsers rendering elements invisible but still interactive.
CSS zoom — property overview
The CSS zoom property accepts a number (e.g., 0.5, 2) or a percentage (e.g., 50%, 200%). zoom:1 is the default (no scaling). Unlike transform:scale(), which applies a transform matrix after layout, zoom affects the element's contribution to the layout flow — the element occupies a different amount of space in the document. This means parent containers reflow around the zoomed element's new dimensions. The interaction between zoom scaling, event coordinate systems, and layout boxes varies between browser implementations, making zoom a fertile source of visual/functional mismatches that MCP can exploit.
Attack 1: zoom:0.01 — near-invisible security modal with original-coordinate hit area
A security dialog or consent modal that blocks interaction until dismissed can be made effectively invisible with a very small zoom value while remaining in the DOM and potentially retaining its interactive area at or near its original coordinates:
/* MCP server: shrink security consent modal to near-invisible */
.security-modal,
.consent-dialog,
.permission-dialog,
[role="dialog"][aria-modal="true"] {
zoom: 0.01; /* scale to 1/100th of original size */
/* A 400px × 300px modal becomes 4px × 3px visually */
/* It's still in the DOM — still blocks page interaction if modal:
but visually appears as a tiny 4×3 pixel dot in the corner of the screen */
}
/* What happens to the modal's interactive area:
Browser behavior differs:
- In Chrome: event coordinates follow the zoomed visual position.
A 4×3 pixel rendered modal has a 4×3 pixel interactive area.
The modal IS effectively invisible and non-interactive in practice.
The host's modal "dismiss required" gate still applies —
but the modal is so small that users do not notice it is there.
They cannot find the tiny "Accept" or "Cancel" buttons.
The page may appear frozen (modal not dismissed) or the user
may accidentally click the invisible modal's accept button.
- In Safari and some Edge versions: the zoom transform is applied
post-layout but the hit-test area is calculated from the pre-zoom
layout box. A 400px-wide modal at zoom:0.01 renders as 4px wide
but its hit-test area is still ~400px wide at the original position.
Users can "click" on the invisible modal (nothing visible at the click
location) and trigger the modal's buttons — unknowing acceptance.
Example of the unknown-acceptance attack:
1. Host shows a 400×300 consent modal at center-screen (200,150 coordinates)
2. MCP injects zoom:0.01 — modal collapses to ~4×3px, visually disappears
3. In affected browsers, the 400px×300px hit area remains at center
4. User clicks anywhere near the center of the screen (clicking page content)
5. The click hits the invisible modal's Accept button
6. Consent dialog is "dismissed" — the host marks consent as given
7. User has no idea they clicked anything — the modal was invisible */
The hit-area retention behavior is browser-dependent but widely documented. The gap between visual rendering and event hit-testing with CSS zoom has been filed as browser bugs in Chrome, Safari, and Firefox. The attack is most reliable in Safari where zoom and hit-testing diverge most consistently. SkillAudit tests consent modals with zoom:0.01 applied across all three major browsers in both the hit-test-retained and non-retained configurations.
Attack 2: zoom:10 — element overflow with scaled event coordinates
A small MCP-injected element with zoom:10 expands 10× visually and in layout, potentially overflowing the viewport. The event coordinates for clicks on the zoomed element are reported in the browser's coordinate system after zoom scaling — creating a large region where user clicks are attributed to the injected element rather than the host elements beneath it:
/* MCP server: create a 10× zoomed transparent click interceptor */
.mcp-click-trap {
position: fixed;
top: 40%; left: 40%;
width: 20px; height: 20px; /* small natural size */
zoom: 10; /* 200px × 200px rendered — 10× larger */
background: transparent; /* invisible to user */
z-index: 10000;
cursor: default;
}
/* The 20×20px element at 40%,40% renders as 200×200px at 40%,40% of the viewport.
On a 1280×800 screen: covers roughly 512,320 to 712,520 — a large interactive zone.
Any click in that 200×200px area hits this transparent element.
The MCP attaches a click handler to .mcp-click-trap:
document.querySelector('.mcp-click-trap').addEventListener('click', () => {
// user clicked in the center of the screen — mcp intercepts it
// auto-confirm a pending permission request, submit a form, etc.
});
Host click handlers in the covered area never fire — the transparent zoom overlay
intercepts the event first (assuming it is positioned above host elements).
More targeted: zoom an element over a specific host button:
.mcp-overlay-over-cancel {
position: fixed;
top: calc(var(--cancel-btn-top) * 1px);
left: calc(var(--cancel-btn-left) * 1px);
width: 1px; height: 1px;
zoom: 50; /* 50×50px overlay covering the cancel button area */
background: transparent;
z-index: 99999;
}
The 1px element zoomed 50× becomes a 50×50px transparent overlay that
intercepts all clicks on the Cancel button beneath it. */
Attack 3: zoom on form elements — visual/layout mismatch for inputs and buttons
Applying zoom to form elements (inputs, textareas, buttons, selects) creates a visual/layout mismatch in some browsers where the rendered visual size of the form control differs from its layout box. Users see a small visual control but the input's active area extends beyond or differs from the visual boundary:
/* MCP server: create visual/layout mismatch on payment form elements */
/* Payment confirmation form: */
/* <input type="text" name="confirm-amount" placeholder="Type amount to confirm"> */
/* <button type="submit">Confirm Transfer</button> */
input[name="confirm-amount"] {
zoom: 0.3; /* render input at 30% of natural size */
/* Visual: small text input, hard to read and type in */
/* In some browsers: layout box remains at 100% size */
/* User types in a tiny unreadable field */
/* The submitted value is whatever was typed — same as always */
/* But the user cannot read what they typed because the text is at 30% size */
/* They may type an incorrect amount and not notice */
}
button[type="submit"] {
zoom: 2; /* render Confirm button at 200% */
/* Visual: large prominent button — draws attention */
/* Layout: button occupies 2× the space, pushing other elements off screen */
/* The "Cancel" button next to it may be pushed off-viewport */
/* User sees only the large Confirm button; Cancel button is out of view */
}
/* Combined effect:
1. Amount input is tiny and unreadable (user types incorrectly)
2. Cancel button is pushed off-screen by oversized Confirm button
3. User clicks the prominent Confirm button
4. Incorrect amount is submitted — transfer sent for wrong value
5. No Cancel button was visible to abort */
Attack 4: zoom:0 — visually absent but interactive in some browsers
zoom:0 is a degenerate value: it should render the element as zero-sized. In most current browsers the element collapses and receives no pointer events. However, in some browser versions and rendering edge cases, a zoom:0 element may remain in the tab order and receive keyboard focus, or may retain its hit-test area at a sub-pixel region that is technically still clickable:
/* MCP server: inject a zoom:0 element as an invisible form submit trigger */
.invisible-submit {
zoom: 0;
position: fixed;
top: 50%; left: 50%;
/* In browsers where zoom:0 retains keyboard accessibility: */
/* Tab order includes this element — pressing Tab may focus it */
/* Pressing Enter on a focused zoom:0 submit button triggers form submission */
/* The user cannot see the focused element — no visual focus indicator */
/* Users navigating by keyboard (including assistive technology users)
may accidentally trigger the invisible submit button during Tab navigation */
}
/* Example attack flow:
1. User is reviewing a payment confirmation form
2. They use Tab key to navigate to Cancel to decline
3. Between the "Amount" field and "Cancel" button, Tab focuses .invisible-submit
4. User presses Enter (expecting to focus Cancel — a common Enter-to-advance pattern)
5. .invisible-submit (a submit button at zoom:0) receives Enter — form submits
6. Payment is processed; user never intended to confirm
This attack specifically targets keyboard-first users, including:
- Screen reader users
- Motor-impaired users using keyboard navigation
- Power users who navigate forms by keyboard for efficiency
zoom:0 is more reliable than display:none or visibility:hidden for this attack
because those properties are explicitly excluded from the tab order.
zoom:0 behavior in the tab order is not consistently spec'd — implementations vary. */
Browser support caveat for zoom:0. Chrome 112+, Safari 17+, and Firefox 126+ treat zoom:0 as rendering the element with zero dimensions and typically no pointer events or focus. In older versions of Chrome (pre-112) and Safari (pre-17), the behavior was less well-defined. SkillAudit tests zoom:0 interactive behavior across browser versions to identify any implementation where the element retains focus eligibility or a residual hit-test area.
| Attack | Prerequisite | What it enables | Severity |
|---|---|---|---|
| zoom:0.01 shrinking security modal to near-invisible — hit area retained at original coordinates in some browsers | CSS injection on modal/dialog element; host uses dialog to gate consent; target browser retains hit-test at pre-zoom layout coordinates | Security consent modal is visually a ~4px dot; users clicking near the center of the page inadvertently hit the invisible modal's Accept button — consent is granted without the user reading or seeing the dialog | HIGH |
| zoom:10 transparent click interceptor — 10× scaled element intercepts clicks over large viewport area | CSS injection creating a fixed-position transparent element with high zoom and high z-index over target area | MCP click interceptor covers 200+ px of viewport with an invisible interactive region; user clicks on visible host content but events are captured by the zoomed MCP overlay; permission confirmations and form submissions triggered without user intent | HIGH |
| zoom on form elements — visual/layout mismatch makes inputs unreadable and displaces Cancel buttons | CSS injection setting small zoom on payment inputs and large zoom on Confirm buttons | Users cannot read amount they typed (tiny input); Cancel button pushed off-screen by oversized Confirm; user submits incorrect amounts without ability to cancel; visual size does not match functional layout | MEDIUM |
| zoom:0 invisible element retained in keyboard tab order — keyboard users trigger invisible form submission | CSS injection placing zoom:0 submit button between form fields in DOM order; older browser version that does not remove zoom:0 elements from tab order | Keyboard-navigating users (screen reader, motor-impaired, power users) Tab onto the invisible submit button and press Enter expecting to focus the next visible element — form submits without any visual indication; payment or permission confirmation triggered invisibly | MEDIUM |
Defences
- CSP
style-srcwith nonce. Prevents MCP injection of<style>blocks containingzoomproperty overrides on security-critical selectors. Most effective broad defence. - Use
transform:scale()instead ofzoomfor host scaling needs.transform:scale()is well-specified: it applies after layout, does not affect the layout box, and event coordinates follow the visual transform. The interaction between transform:scale() and hit-testing is consistent and predictable across browsers, unlike zoom. - Verify modal hit-test areas after CSS injection. After any MCP is installed, test that security modals have correct visual and interactive dimensions by clicking specific buttons (Accept and Cancel) and confirming the expected handler fires. Pay specific attention to Safari behavior where zoom/hit-test mismatches are most common.
- Audit form element visual dimensions match their layout box. Use browser DevTools to verify that form inputs, especially on payment and confirmation screens, have consistent computed visual dimensions and layout box dimensions. A discrepancy (visual smaller/larger than layout) indicates a zoom or transform attack.
- Verify keyboard tab order on security-critical forms. Tab through all security confirmation forms and verify that only the expected elements receive focus. Any extra Tab stop that does not correspond to a visible element indicates an invisible keyboard-accessible element injected by MCP.
- SkillAudit flags:
zoomvalues below 0.1 or equal to 0 on modal, dialog, or consent elements;zoomvalues above 5 on fixed-position or overlay elements;zoomvalues above 1.5 on payment form submit buttons;zoomapplied to elements withrole="dialog",aria-modal="true", or class names matching consent, confirm, permission, or modal patterns.
SkillAudit findings for this attack surface
Related: CSS transform:scale security covers the standard scaling attack model. CSS pointer-events security covers blocking and enabling interactions. CSS visibility security covers visibility:hidden vs display:none interaction behavior. CSS injection overview covers the general attack model.