Service Worker Security Considerations Explained
Service Worker, as a proxy server running in the browser background, has powerful capabilities while also bringing some security risks. Understanding these security issues is crucial for developing secure web applications.
1. HTTPS Requirement
Why HTTPS is Mandatory
javascript// Service Worker can only be registered in HTTPS environment // Exception: localhost allows HTTP if ('serviceWorker' in navigator) { // Check if it's a secure context if (window.isSecureContext) { navigator.serviceWorker.register('/sw.js'); } else { console.error('Service Worker requires HTTPS environment'); } }
Security Risks:
- Service Worker in HTTP environment may be tampered by man-in-the-middle attacks
- Attackers can inject malicious Service Workers to intercept all network requests
- Users' sensitive data may be stolen
Detect Secure Context
javascript// Check if current environment is secure function checkSecureContext() { if (!window.isSecureContext) { console.warn('Not in secure context, Service Worker functionality limited'); return false; } return true; } // Or check protocol function isSecureProtocol() { return location.protocol === 'https:' || location.hostname === 'localhost'; }
2. Scope Limitations
Scope Security
javascript// Service Worker can only control pages within its scope // Specify scope during registration navigator.serviceWorker.register('/sw.js', { scope: '/app/' // Can only control pages under /app/ }); // Attempting to access resources outside scope will fail // /app/page.html ✅ Can control // /other/page.html ❌ Cannot control
Path Traversal Protection
javascript// ❌ Dangerous: Not validating paths may cause security issues self.addEventListener('fetch', event => { const url = new URL(event.request.url); // Malicious requests may contain path traversal characters like ../ caches.match(url.pathname); // Dangerous! }); // ✅ Safe: Validate and sanitize paths self.addEventListener('fetch', event => { const url = new URL(event.request.url); const pathname = url.pathname; // Validate path doesn't contain traversal characters if (pathname.includes('..') || pathname.includes('//')) { event.respondWith(new Response('Invalid path', { status: 400 })); return; } // Only allow access to whitelist paths const allowedPaths = ['/api/', '/assets/', '/static/']; const isAllowed = allowedPaths.some(path => pathname.startsWith(path)); if (!isAllowed) { event.respondWith(new Response('Forbidden', { status: 403 })); return; } event.respondWith(caches.match(event.request)); });
3. Content Security Policy (CSP)
CSP Impact on Service Worker
javascript// Set CSP to prevent XSS attacks // Set in HTTP response header: // Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' // Service Worker script itself needs to comply with CSP // Inline scripts may be blocked
Safe Service Worker Script Loading
javascript// ✅ Recommended: Load external script navigator.serviceWorker.register('/sw.js'); // ❌ Avoid: Use inline Service Worker const swCode = ` self.addEventListener('fetch', ...); `; const blob = new Blob([swCode], { type: 'application/javascript' }); const url = URL.createObjectURL(blob); navigator.serviceWorker.register(url); // May violate CSP
4. Cache Security
Sensitive Data Caching
javascript// ❌ Dangerous: Caching sensitive information self.addEventListener('fetch', event => { if (event.request.url.includes('/api/user/profile')) { event.respondWith( fetch(event.request).then(response => { // Cache response containing personal information caches.open('api-cache').then(cache => { cache.put(event.request, response.clone()); // Dangerous! }); return response; }) ); } }); // ✅ Safe: Don't cache sensitive data const SENSITIVE_PATHS = [ '/api/auth/', '/api/user/profile', '/api/payment/' ]; self.addEventListener('fetch', event => { const url = new URL(event.request.url); // Check if it's a sensitive path const isSensitive = SENSITIVE_PATHS.some(path => url.pathname.includes(path) ); if (isSensitive) { // Sensitive requests go directly to network, no caching event.respondWith(fetch(event.request)); return; } // Non-sensitive requests can use cache event.respondWith( caches.match(event.request).then(response => { return response || fetch(event.request); }) ); });
Safe Cache Cleanup
javascript// ✅ Safe cache cleanup self.addEventListener('activate', event => { event.waitUntil( caches.keys().then(cacheNames => { return Promise.all( cacheNames.map(cacheName => { // Only delete caches created by current Service Worker // Avoid deleting other apps' caches if (cacheName.startsWith('my-app-')) { return caches.delete(cacheName); } }) ); }) ); });
5. Cross-Site Scripting (XSS) Protection
Prevent Malicious Script Injection
javascript// ❌ Dangerous: Directly using user input self.addEventListener('message', event => { const userInput = event.data.message; // Directly executing user input may cause XSS eval(userInput); // Extremely dangerous! }); // ✅ Safe: Validate and sanitize input self.addEventListener('message', event => { const data = event.data; // Verify message origin if (event.origin !== 'https://trusted-domain.com') { console.error('Untrusted origin:', event.origin); return; } // Verify data type if (typeof data.action !== 'string') { return; } // Use whitelist to validate action const allowedActions = ['skipWaiting', 'claimClients']; if (!allowedActions.includes(data.action)) { console.error('Invalid action:', data.action); return; } // Safe execution switch (data.action) { case 'skipWaiting': self.skipWaiting(); break; case 'claimClients': self.clients.claim(); break; } });
6. Man-in-the-Middle Attack Protection
Certificate Pinning
javascript// Although Service Worker cannot directly implement certificate pinning // Can enhance security by checking response headers self.addEventListener('fetch', event => { event.respondWith( fetch(event.request).then(response => { // Check security response headers const headers = response.headers; // Check HSTS header if (!headers.get('Strict-Transport-Security')) { console.warn('Missing HSTS header'); } // Check CSP header if (!headers.get('Content-Security-Policy')) { console.warn('Missing CSP header'); } return response; }) ); });
7. Permission Control
Principle of Least Privilege
javascript// ✅ Only request necessary permissions // Push notification permission async function requestPushPermission() { const permission = await Notification.requestPermission(); return permission === 'granted'; } // Background sync permission async function requestSyncPermission() { const registration = await navigator.serviceWorker.ready; if ('sync' in registration) { // Check permission status const status = await navigator.permissions.query({ name: 'periodic-background-sync' }); if (status.state === 'granted') { return true; } } return false; }
8. Update Security
Safe Update Mechanism
javascript// ✅ Verify Service Worker updates navigator.serviceWorker.register('/sw.js').then(registration => { registration.addEventListener('updatefound', () => { const newWorker = registration.installing; newWorker.addEventListener('statechange', () => { if (newWorker.state === 'installed') { // Verify new version integrity // Can verify by computing hash value verifyServiceWorkerIntegrity(newWorker).then(isValid => { if (isValid) { // Prompt user to update showUpdateNotification(newWorker); } else { console.error('Service Worker integrity verification failed'); } }); } }); }); }); // Verify Service Worker integrity (example) async function verifyServiceWorkerIntegrity(worker) { try { const response = await fetch('/sw.js'); const scriptContent = await response.text(); // Can add hash verification logic here // For example, compare with server-returned hash value return true; } catch (error) { console.error('Verification failed:', error); return false; } }
9. Data Leak Prevention
Prevent Cache Leaks
javascript// ✅ Safe caching strategy const PUBLIC_RESOURCES = [ '/', '/index.html', '/styles.css', '/app.js', '/images/' ]; const PRIVATE_RESOURCES = [ '/api/user/', '/api/orders/', '/dashboard/' ]; self.addEventListener('fetch', event => { const url = new URL(event.request.url); // Check if it's a public resource const isPublic = PUBLIC_RESOURCES.some(path => url.pathname.startsWith(path) ); // Check if it's a private resource const isPrivate = PRIVATE_RESOURCES.some(path => url.pathname.startsWith(path) ); if (isPrivate) { // Private resources: network first, no caching event.respondWith( fetch(event.request).catch(() => { return new Response('Network error', { status: 503 }); }) ); } else if (isPublic) { // Public resources: can use cache event.respondWith( caches.match(event.request).then(response => { return response || fetch(event.request); }) ); } else { // Unknown resources: default network request event.respondWith(fetch(event.request)); } });
10. Security Best Practices Checklist
Development Stage
- Ensure all environments use HTTPS (except localhost)
- Set reasonable Service Worker scope
- Don't cache sensitive data (user info, payment data, etc.)
- Validate all user input
- Implement CSP policy
- Regularly update Service Worker
Production Environment
- Monitor Service Worker abnormal behavior
- Implement Subresource Integrity (SRI)
- Configure HSTS headers
- Regularly audit cache contents
- Implement access control
- Record security logs
Security Testing Recommendations
javascript// Security testing checklist const securityTests = { // 1. Check HTTPS checkHTTPS: () => location.protocol === 'https:', // 2. Check scope checkScope: async () => { const registration = await navigator.serviceWorker.ready; console.log('Service Worker scope:', registration.scope); return registration.scope; }, // 3. Check cache contents checkCacheContents: async () => { const cacheNames = await caches.keys(); for (const name of cacheNames) { const cache = await caches.open(name); const requests = await cache.keys(); console.log(`Cache "${name}" contains ${requests.length} items`); } }, // 4. Check permissions checkPermissions: async () => { const permissions = await navigator.permissions.query({ name: 'notifications' }); console.log('Notification permission:', permissions.state); } };
Summary
Service Worker security key points:
- Must use HTTPS: Prevent man-in-the-middle attacks
- Set reasonable scope: Limit control capabilities
- Don't cache sensitive data: Prevent data leakage
- Validate user input: Prevent XSS attacks
- Implement CSP: Enhance content security
- Regular updates: Fix security vulnerabilities
- Least privilege: Only request necessary permissions