Security Guide
MCP server Content Index API security — offline content enumeration, service worker surveillance, and user interest inference
The Content Index API allows service workers to register content for offline availability and expose it in the browser's offline content UI. registration.index.getAll() returns every item registered at the origin — article titles, URLs, descriptions, categories, and icons. MCP server tool output that can reach the service worker registration can enumerate this index to map the user's saved reading material: what topics they're researching, what articles they've bookmarked for offline access, and what categories of content they consume. This is passive browsing history inference from structured metadata, requiring no interaction beyond injecting the enumeration script.
What the Content Index API exposes
The Content Index API (Chrome 84+, available on Android Chrome) is designed for news readers, recipe apps, and any PWA that wants users to access content while offline. An application registers articles and pages via registration.index.add() with a descriptor that includes the content's title, description, URL, category ('article', 'video', 'audio', 'homepage'), icons, and publication date. The browser surfaces this in its offline content browser, and the service worker provides the cached content when offline.
The attack surface: registration.index.getAll() is readable by any same-origin JavaScript that can obtain the service worker registration. The returned array contains complete descriptors for every piece of content the user has saved for offline reading at the origin — a direct window into the user's content consumption history.
// Content Index enumeration — reads all user-saved offline content
// Requires access to service worker registration (same-origin)
async function enumerateContentIndex() {
const registration = await navigator.serviceWorker.ready;
// getAll() returns all content registered for offline access at this origin
const contentItems = await registration.index.getAll();
// Each item contains:
// { id, title, description, url, category, icons: [{src, sizes, type}] }
const profile = contentItems.map(item => ({
title: item.title, // "How to Migrate from OpenAI to Claude API"
description: item.description, // Article summary — topic and detail level
url: item.url, // Full URL — reveals source publication and topic
category: item.category, // 'article' | 'video' | 'audio' | 'homepage'
// launch URL if different from content URL (may reveal additional path params)
launchUrl: item.launchUrl
}));
// This array reveals:
// - Research topics the user is investigating
// - Publications and sources the user reads
// - Content categories (news, technical, financial, political)
// - Recency of interest (items added recently vs long ago)
navigator.sendBeacon('/track', JSON.stringify({
contentIndex: profile,
count: profile.length
}));
}
Content Index items reveal structured interest profiles with semantic labels. Unlike browser history (URLs without descriptions) or cache entries (opaque blobs), Content Index items include human-readable titles and descriptions — the same metadata the user sees when browsing offline content. An attacker who reads this array gets a curated, semantically labeled view of what the user intended to read and research, without any interaction beyond the injection.
Modifying the Content Index: polluting offline content
Beyond reading the index, injected code can add items to it via registration.index.add(). Adding attacker-controlled items to the Content Index has two effects: (1) the items appear in the browser's offline content UI, potentially confusing the user about what they saved, and (2) the service worker may cache and serve attacker-supplied content under the origin, creating a persistent content injection that survives until explicitly removed.
This is distinct from Cache Storage manipulation because Content Index items appear in a dedicated browser UI (the offline content browser on Android Chrome) that users may treat as authoritative "saved reading" from their application. Injecting plausible-looking articles into this list could direct users to attacker-controlled content the next time they browse their offline reading list.
| Attack | Content Index surface | What it enables |
|---|---|---|
| Offline content enumeration | registration.index.getAll() |
All saved articles/videos with titles, descriptions, URLs — user interest profile |
| Browsing pattern inference | Content categories and URLs | Topics, sources, and content types reveal research context and media habits |
| Content Index pollution | registration.index.add() |
Injects attacker content into user's offline reading list — content confusion and redirection |
| Content Index deletion DoS | registration.index.delete(id) |
Removes user's saved offline content — data loss that disrupts offline workflows |
Permissions-Policy gap and defenses
There is no Permissions-Policy directive for the Content Index API. The API is accessible to any same-origin JavaScript with a service worker registration. The primary defenses are: (1) cross-origin sandboxed iframe rendering for MCP tool output — injected code runs at a different origin and cannot reach the application's service worker registration or content index; (2) CSP script-src with nonces — prevents the injection from executing the enumeration script; (3) MCP applications that do not use a service worker at all have no content index exposure, since navigator.serviceWorker.ready returns a pending promise that never resolves if no SW is registered at the scope.
MCP deployments without a service worker are not exposed to Content Index attacks. The Content Index API only has data if the application uses a service worker and that SW calls index.add(). MCP clients that rely solely on HTTP caching and do not register a service worker have no Content Index to enumerate. This makes Content Index a tier-2 risk that applies specifically to PWA-based MCP clients.
SkillAudit findings for Content Index API exposure
Audit your MCP server for Content Index and service worker risks
SkillAudit checks for tool output isolation, service worker attack surfaces, and Content Index exposure — paste a GitHub URL and get a graded security report in 60 seconds.
Run a free audit →