Blog · MCP Server Security
MCP server Screen Wake Lock API security — wake lock as user behavior oracle, battery drain DoS, lock release on page visibility change, and permission model
The Screen Wake Lock API (navigator.wakeLock.request('screen')) prevents the device screen from turning off during long-running MCP tool executions — a useful ergonomic feature for multi-minute audits and analysis runs. But the wake lock lifecycle exposes user behavior: the browser releases a screen wake lock automatically when the user switches to another tab or locks their screen, and the release event is observable by JavaScript. An MCP tool that instruments this event can infer when the user is actively watching the page versus absent — a low-resolution presence oracle with no permission requirement beyond having a wake lock.
Wake lock release as a user presence oracle
The WakeLockSentinel returned by navigator.wakeLock.request() fires a release event whenever the OS releases the lock. The browser releases screen wake locks automatically when the document becomes hidden — when the user switches tabs, minimizes the browser window, or locks the device screen. The release timestamp is observable:
// Wake lock as user presence oracle — observable by any script on the page
const sentinel = await navigator.wakeLock.request('screen');
sentinel.addEventListener('release', () => {
const releaseTime = Date.now();
// User switched tabs or locked screen at releaseTime
// This is a behavioral signal: the user was present until this timestamp
// ... can be logged, can be exfiltrated, no additional permission required
logUserPresence({ event: 'screen_hidden', at: releaseTime });
});
// Pair with visibilitychange for confirmation
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
// User returned — re-request wake lock
navigator.wakeLock.request('screen').then(s => { /* ... */ });
}
});
Privacy implication: An MCP server that instructs its client UI to hold a wake lock and report release events can build a presence timeline of the user — when they are actively at their desk versus away, based on tab-switch patterns. No clipboard, camera, microphone, or location permission is required. The signal is coarser than keystroke timing but finer than session-level analytics.
Battery drain DoS via multiple wake locks
A single screen wake lock is released automatically when the page becomes hidden. But a malicious MCP tool can trigger a large number of concurrent wake lock requests. While the browser OS prevents the screen from turning off for each lock, holding many locks simultaneously consumes battery. On mobile devices, continuous screen-on prevents the CPU from entering deep sleep states, significantly increasing power consumption:
// Dangerous: MCP tool response triggers multiple wake lock requests in a loop
// (via injected script in tool output rendered without sanitization)
async function maliciousWakeLocks() {
const locks = [];
for (let i = 0; i < 50; i++) {
try {
locks.push(await navigator.wakeLock.request('screen'));
} catch {}
}
// 50 active screen wake locks — screen stays on permanently, battery drains
// locks are not released until page is closed
}
// Safe: MCP UI acquires at most one wake lock; releases when tool completes
let activeLock = null;
async function acquireWakeLockForTool() {
if (activeLock) return; // already have one — don't acquire another
try {
activeLock = await navigator.wakeLock.request('screen');
activeLock.addEventListener('release', () => { activeLock = null; });
} catch {}
}
async function releaseWakeLock() {
if (activeLock) {
await activeLock.release();
activeLock = null;
}
}
Re-acquisition on visibilitychange as focus tracking
The standard pattern for wake locks is to re-acquire the lock when the page becomes visible again after being hidden. This pattern is documented in the W3C spec. Legitimate use case: the MCP tool is still running and should keep the screen on when the user returns. But combined with an event log, re-acquisition on visibilitychange creates a tab-focus tracking mechanism — the script knows exactly when the user returns to the tab and how long they were away:
// This pattern is used for user tracking — log each re-acquisition
document.addEventListener('visibilitychange', async () => {
if (document.visibilityState === 'visible') {
const returnTime = Date.now();
const absentDuration = returnTime - lastHiddenAt;
// absentDuration reveals user's away time — can be exfiltrated
trackUserBehavior({ event: 'returned', absentMs: absentDuration });
// ... re-acquire wake lock for legitimate purpose
} else {
lastHiddenAt = Date.now();
}
});
Defense: MCP UIs that use wake locks should not log re-acquisition events for analytics. If the tool execution is complete when the user returns, do not re-acquire the lock. Limit wake locks to the duration of the active tool execution and release promptly on completion.
Security comparison: Screen Wake Lock patterns for MCP UIs
| Pattern | Security risk | Mitigation |
|---|---|---|
| Wake lock release event logged with timestamp | User presence oracle — reveals when user switched away from page | Do not log wake lock release events; release silently without tracking |
| Multiple wake lock requests from tool output | Battery drain DoS — many simultaneous screen locks prevent device sleep | Maximum one active wake lock per page; guard with a sentinel check before requesting |
| visibilitychange + wake lock re-acquisition with event logging | Tab-focus duration tracking — exfiltrates time user spends on page vs away | Do not log re-acquisition events; only re-acquire if tool execution is still active |
| Wake lock held after tool completes | Screen stays on indefinitely — battery drain without user benefit | Release wake lock in tool completion handler and in error/abort handlers |
| Wake lock requested without user interaction | Some browsers deny wake lock requests without a user gesture — fail silently | Request wake lock in response to user action (Start Audit button); handle rejection gracefully |
SkillAudit findings
release events with timestamps and sends them to analytics or MCP server. Creates a user presence timeline — when user is actively watching the tool execution vs absent. No additional permission required beyond the wake lock itself. −16 pts
See also: MCP server PerformanceObserver security (other user-behavior timing side channels) · MCP server IntersectionObserver security (element visibility as user presence signal)