Blog · MCP Server Security

MCP server Web Share API security — navigator.share() data exposure, share target registration attack surface, and URL injection in shared payloads

MCP UIs that render tool output as shareable content — audit reports, analysis summaries, code snippets — may invoke navigator.share() to let users share results via the OS share sheet. The Web Share API passes data to any installed share target including untrusted apps. Tool output that contains sensitive data (API keys in error messages, personal data in query results, internal URLs) exits the browser sandbox when shared. A URL in the share payload controlled by tool output creates a phishing vector that bypasses origin-level URL validation.

navigator.share() data flow and MCP tool output exposure

The Web Share API's data model accepts three fields: title, text, and url, plus a files array. When an MCP UI calls navigator.share({ text: toolResult.summary }), the operating system presents a share sheet with all installed apps that have registered as share targets — including messaging apps, email clients, note-taking apps, cloud storage services, and any other app the user has installed. The user selects a destination, and the data is passed to that app.

// Dangerous: raw tool result passed to navigator.share()
async function shareToolResult(toolResult) {
  await navigator.share({
    title: 'SkillAudit Report',
    text: toolResult.fullReport,  // may contain API keys, tokens, internal URLs
    url: toolResult.reportUrl     // may be attacker-controlled if tool output is untrusted
  });
}

// Safe: sanitize and scope what gets shared
async function shareToolResult(toolResult) {
  // Share only the public summary — never raw output
  const safeText = toolResult.publicSummary.slice(0, 500); // length limit
  const safeUrl = buildCanonicalReportUrl(toolResult.reportId); // server-generated, not from tool output

  if (!safeUrl.startsWith('https://skillaudit.dev/')) {
    throw new Error('Share URL must be a canonical report URL');
  }

  await navigator.share({ title: 'SkillAudit Report', text: safeText, url: safeUrl });
}

URL injection in shared payloads

The url field in navigator.share() is rendered as a clickable link in many share targets (messaging apps, email). If the URL is derived from MCP tool output without validation, a malicious MCP server or a compromised tool response can inject an attacker-controlled URL into the shared payload. The user sees the share come from the legitimate MCP UI (the OS share sheet shows the originating app name) and may trust the URL.

Attack scenario: MCP tool returns a "report URL" field that is displayed in the UI and used in navigator.share(). Attacker-controlled tool output sets reportUrl to a phishing page at https://skillaudit-reports.attacker.com/ (typosquatted domain). The user shares the result via their messaging app; recipients click the link and reach the attacker's page. The share originates from the legitimate MCP UI — no browser address bar visible in the share target.

Defense: the URL passed to navigator.share() must be server-generated and validated before use. Never pass a URL from tool output directly to the share payload:

// Validate URL before sharing — must be on your own origin
function validateShareUrl(url) {
  try {
    const parsed = new URL(url);
    if (parsed.origin !== 'https://skillaudit.dev') {
      throw new Error(`Share URL must be on skillaudit.dev, got: ${parsed.origin}`);
    }
    return parsed.href;
  } catch {
    return 'https://skillaudit.dev/'; // safe fallback
  }
}

Share Target API: persistent browser entry point

The Web Share Target API (the receiving side) lets a PWA register as a share target via its Web App Manifest. A registered MCP UI receives shared content from other apps via a GET or POST request to a manifest-defined URL, with shared data in query parameters or form body. This creates a persistent entry point: once a user has added the MCP PWA to their home screen and accepted the share target registration, any other app on the device can send data to the MCP UI without further user confirmation beyond selecting it from the share sheet.

// Web App Manifest share_target registration
{
  "share_target": {
    "action": "/share-target",
    "method": "POST",
    "enctype": "multipart/form-data",
    "params": {
      "title": "title",
      "text": "text",
      "url": "url",
      "files": [{ "name": "media", "accept": ["image/*", "application/pdf"] }]
    }
  }
}

// Server-side share target handler — validate everything received
app.post('/share-target', upload.single('media'), (req, res) => {
  const { title, text, url } = req.body;
  // title, text, url come from an UNTRUSTED source — any app on the device
  // Treat as untrusted user input: sanitize, validate, escape before rendering
  const safeTitle = sanitizeHtml(title, { allowedTags: [] }).slice(0, 200);
  const safeText = sanitizeHtml(text, { allowedTags: [] }).slice(0, 5000);
  // url: validate scheme and never auto-navigate to it
  if (url && !url.startsWith('https://')) {
    return res.status(400).send('Invalid URL scheme');
  }
  // Process the share — do not render raw values into HTML
});

File sharing and temporary URL exposure

The files parameter of navigator.share() allows sharing File objects. When an MCP UI creates a File from tool output (e.g., a generated report) and passes it to navigator.share({ files }), the browser may create a temporary URL for the file. This URL may be logged by the OS share sheet infrastructure, cached in app clipboard history, or exposed in OS-level sharing logs. Files containing sensitive tool output should not be shared via the Web Share API; instead, generate a time-limited signed URL on the server and share that URL.

Security comparison: Web Share API patterns for MCP UIs

PatternSecurity riskMitigation
Raw tool output as navigator.share({ text }) Sensitive data (tokens, PII, internal URLs) sent to untrusted OS share targets Share only public summaries; never raw tool output; redact before sharing
Tool output URL as navigator.share({ url }) Attacker-controlled URL injected into share payload — phishing via share sheet Server-generate share URLs; validate origin matches your domain before passing to share()
Share Target receiving raw data without validation Any device app can send arbitrary data to your MCP UI — treated as user input Treat all share target input as untrusted; sanitize, validate, length-limit before processing
Sharing File objects containing sensitive tool output File data exits browser via OS share infrastructure — may be logged or cached by OS Generate server-side signed URLs instead of sharing File objects with sensitive content
navigator.canShare() used as capability gate without data validation canShare() only checks MIME type support — does not validate the data content Validate share data independently of canShare() result

SkillAudit findings

High MCP tool output passed directly to navigator.share({ text }) without sanitization. Tool results containing API credentials, session tokens, or personal data are transmitted to OS-level share targets that may be untrusted apps. −18 pts
High URL from MCP tool output passed to navigator.share({ url }) without origin validation. Compromised tool server returns attacker-controlled URL that is shared via the OS share sheet with full trust of the originating MCP application. −16 pts
Medium Web Share Target handler renders received text or title fields without HTML sanitization. Any installed app on the device can send arbitrary HTML/script content to the MCP UI via the share target endpoint. −12 pts
Medium File objects containing full tool results (including sensitive data) shared via navigator.share({ files }). File contents exit the browser sandbox via OS share sheet infrastructure without redaction. −10 pts

See also: MCP server Clipboard API security (clipboard data poisoning via tool output) · MCP server fetch() security (URL validation patterns)