Back to blog
SecurityJanuary 1, 2024· 16 min read

Browser Storage Security and Limits: A Developer's Guide

A practical Fusebox guide to browser storage security and limits.

Browser Storage Security and Limits: A Developer's Guide

Browser storage has evolved from simple cookies to sophisticated APIs like IndexedDB. Yet with great power comes great responsibility—and risk. In 2021, a major news website accidentally exposed user preferences and reading history through improperly secured localStorage, affecting millions. Understanding browser storage security isn't optional—it's critical for protecting user data.

Why Browser Storage Security Matters

Modern web applications store everything from user preferences to authentication tokens in browser storage. A single XSS vulnerability can turn these storage mechanisms into data goldmines for attackers. With storage limits varying wildly across browsers and devices, developers must balance functionality with security and reliability.

Comprehensive Storage Analyzer

Start with this complete browser storage analyzer:

// Comprehensive Browser Storage Analyzer
const StorageAnalyzer = {
    // Analyze all storage mechanisms
    async analyzeAllStorage() {
        const analysis = {
            timestamp: new Date().toISOString(),
            summary: {},
            details: {},
            security: {},
            limits: {},
            recommendations: []
        };
        
        // Analyze each storage type
        analysis.details.cookies = this.analyzeCookies();
        analysis.details.localStorage = this.analyzeLocalStorage();
        analysis.details.sessionStorage = this.analyzeSessionStorage();
        analysis.details.indexedDB = await this.analyzeIndexedDB();
        analysis.details.webSQL = this.analyzeWebSQL();
        analysis.details.cacheStorage = await this.analyzeCacheStorage();
        
        // Test storage limits
        analysis.limits = await this.testStorageLimits();
        
        // Security analysis
        analysis.security = this.analyzeSecurityIssues();
        
        // Generate summary
        analysis.summary = this.generateSummary(analysis);
        
        // Generate recommendations
        analysis.recommendations = this.generateRecommendations(analysis);
        
        return analysis;
    },
    
    // Analyze cookies
    analyzeCookies() {
        const cookies = document.cookie.split(';').filter(c => c.trim());
        const analysis = {
            count: cookies.length,
            totalSize: document.cookie.length,
            cookies: [],
            security: {
                httpOnly: 0,
                secure: 0,
                sameSite: 0
            }
        };
        
        cookies.forEach(cookie => {
            const [name, value] = cookie.trim().split('=');
            const cookieData = {
                name,
                size: cookie.length,
                value: value ? value.substring(0, 20) + '...' : '',
                flags: []
            };
            
            // Note: Can't detect httpOnly, secure, sameSite from JavaScript
            // These would need to be checked via HTTP headers
            
            analysis.cookies.push(cookieData);
        });
        
        // Check for sensitive data patterns
        analysis.sensitiveData = this.checkSensitiveData(document.cookie);
        
        return analysis;
    },
    
    // Analyze localStorage
    analyzeLocalStorage() {
        const analysis = {
            count: localStorage.length,
            totalSize: 0,
            items: [],
            security: {
                encryptedItems: 0,
                sensitiveData: []
            }
        };
        
        for (let i = 0; i < localStorage.length; i++) {
            const key = localStorage.key(i);
            const value = localStorage.getItem(key);
            const size = key.length + value.length;
            
            analysis.totalSize += size;
            
            const item = {
                key,
                size,
                type: this.detectDataType(value),
                encrypted: this.isLikelyEncrypted(value)
            };
            
            if (item.encrypted) {
                analysis.security.encryptedItems++;
            }
            
            // Check for sensitive data
            const sensitive = this.checkSensitiveData(key + value);
            if (sensitive.length > 0) {
                analysis.security.sensitiveData.push({
                    key,
                    patterns: sensitive
                });
            }
            
            analysis.items.push(item);
        }
        
        return analysis;
    },
    
    // Analyze sessionStorage
    analyzeSessionStorage() {
        const analysis = {
            count: sessionStorage.length,
            totalSize: 0,
            items: [],
            security: {
                encryptedItems: 0,
                sensitiveData: []
            }
        };
        
        for (let i = 0; i < sessionStorage.length; i++) {
            const key = sessionStorage.key(i);
            const value = sessionStorage.getItem(key);
            const size = key.length + value.length;
            
            analysis.totalSize += size;
            
            const item = {
                key,
                size,
                type: this.detectDataType(value),
                encrypted: this.isLikelyEncrypted(value)
            };
            
            if (item.encrypted) {
                analysis.security.encryptedItems++;
            }
            
            analysis.items.push(item);
        }
        
        return analysis;
    },
    
    // Analyze IndexedDB
    async analyzeIndexedDB() {
        const analysis = {
            databases: [],
            totalSize: 0,
            security: {
                encrypted: 0,
                unencrypted: 0
            }
        };
        
        if (!window.indexedDB) {
            analysis.supported = false;
            return analysis;
        }
        
        try {
            // Get database names (only works in some browsers)
            const databases = await indexedDB.databases?.() || [];
            
            for (const dbInfo of databases) {
                const db = await this.openDatabase(dbInfo.name);
                const dbAnalysis = {
                    name: dbInfo.name,
                    version: db.version,
                    objectStores: []
                };
                
                const storeNames = Array.from(db.objectStoreNames);
                for (const storeName of storeNames) {
                    const transaction = db.transaction([storeName], 'readonly');
                    const store = transaction.objectStore(storeName);
                    const count = await this.promisify(store.count());
                    
                    dbAnalysis.objectStores.push({
                        name: storeName,
                        keyPath: store.keyPath,
                        autoIncrement: store.autoIncrement,
                        recordCount: count,
                        indexes: Array.from(store.indexNames)
                    });
                }
                
                db.close();
                analysis.databases.push(dbAnalysis);
            }
        } catch (error) {
            analysis.error = error.message;
        }
        
        return analysis;
    },
    
    // Analyze WebSQL (deprecated but still present)
    analyzeWebSQL() {
        const analysis = {
            supported: !!window.openDatabase,
            databases: []
        };
        
        if (analysis.supported) {
            analysis.warning = 'WebSQL is deprecated - migrate to IndexedDB';
        }
        
        return analysis;
    },
    
    // Analyze Cache Storage
    async analyzeCacheStorage() {
        const analysis = {
            supported: 'caches' in window,
            caches: [],
            totalSize: 0
        };
        
        if (!analysis.supported) {
            return analysis;
        }
        
        try {
            const cacheNames = await caches.keys();
            
            for (const name of cacheNames) {
                const cache = await caches.open(name);
                const requests = await cache.keys();
                
                const cacheInfo = {
                    name,
                    entries: requests.length,
                    urls: requests.map(r => r.url)
                };
                
                analysis.caches.push(cacheInfo);
            }
        } catch (error) {
            analysis.error = error.message;
        }
        
        return analysis;
    },
    
    // Test storage limits
    async testStorageLimits() {
        const limits = {
            localStorage: await this.testLocalStorageLimit(),
            sessionStorage: await this.testSessionStorageLimit(),
            indexedDB: await this.testIndexedDBLimit(),
            cookies: this.getCookieLimit()
        };
        
        // Estimate available storage
        if ('storage' in navigator && 'estimate' in navigator.storage) {
            try {
                const estimate = await navigator.storage.estimate();
                limits.quota = {
                    usage: estimate.usage,
                    quota: estimate.quota,
                    percentUsed: (estimate.usage / estimate.quota * 100).toFixed(2)
                };
            } catch (error) {
                limits.quota = { error: error.message };
            }
        }
        
        return limits;
    },
    
    // Test localStorage limit
    async testLocalStorageLimit() {
        const testKey = '__storage_test__';
        let size = 0;
        const chunkSize = 1024 * 100; // 100KB chunks
        
        try {
            // Clear test key first
            localStorage.removeItem(testKey);
            
            while (true) {
                const chunk = 'x'.repeat(chunkSize);
                const currentValue = localStorage.getItem(testKey) || '';
                localStorage.setItem(testKey, currentValue + chunk);
                size += chunkSize;
                
                // Safety limit
                if (size > 50 * 1024 * 1024) { // 50MB
                    break;
                }
            }
        } catch (e) {
            // Quota exceeded
        } finally {
            localStorage.removeItem(testKey);
        }
        
        return {
            approximate: size,
            formatted: this.formatBytes(size)
        };
    },
    
    // Test sessionStorage limit
    async testSessionStorageLimit() {
        const testKey = '__storage_test__';
        let size = 0;
        const chunkSize = 1024 * 100; // 100KB chunks
        
        try {
            sessionStorage.removeItem(testKey);
            
            while (true) {
                const chunk = 'x'.repeat(chunkSize);
                const currentValue = sessionStorage.getItem(testKey) || '';
                sessionStorage.setItem(testKey, currentValue + chunk);
                size += chunkSize;
                
                if (size > 50 * 1024 * 1024) { // 50MB safety limit
                    break;
                }
            }
        } catch (e) {
            // Quota exceeded
        } finally {
            sessionStorage.removeItem(testKey);
        }
        
        return {
            approximate: size,
            formatted: this.formatBytes(size)
        };
    },
    
    // Test IndexedDB limit
    async testIndexedDBLimit() {
        // IndexedDB limits are much larger and vary by browser
        // This is a simplified check
        return {
            note: 'IndexedDB limits vary by browser and available disk space',
            typical: '50% of free disk space',
            minimum: '100MB to 1GB depending on browser'
        };
    },
    
    // Get cookie limits
    getCookieLimit() {
        return {
            maxCookies: 'Varies by browser (typically 50-300 per domain)',
            maxSize: '4KB per cookie',
            totalSize: 'Varies (typically 10KB-100KB total)'
        };
    },
    
    // Security analysis
    analyzeSecurityIssues() {
        const issues = [];
        const score = 100;
        
        // Check for authentication tokens in localStorage
        for (let i = 0; i < localStorage.length; i++) {
            const key = localStorage.key(i);
            const value = localStorage.getItem(key);
            
            if (key.toLowerCase().includes('token') || 
                key.toLowerCase().includes('auth') ||
                value.includes('bearer') ||
                this.looksLikeJWT(value)) {
                issues.push({
                    severity: 'high',
                    type: 'auth-token-in-localstorage',
                    message: 'Authentication tokens should not be stored in localStorage',
                    key: key,
                    recommendation: 'Use httpOnly cookies or sessionStorage for sensitive tokens'
                });
            }
        }
        
        // Check for PII in storage
        const storageContent = JSON.stringify(localStorage) + JSON.stringify(sessionStorage);
        const piiPatterns = this.checkSensitiveData(storageContent);
        
        if (piiPatterns.length > 0) {
            issues.push({
                severity: 'medium',
                type: 'pii-in-storage',
                message: 'Potentially sensitive data found in storage',
                patterns: piiPatterns,
                recommendation: 'Encrypt sensitive data before storing'
            });
        }
        
        // Check for large unencrypted data
        const largeUnencrypted = [];
        for (let i = 0; i < localStorage.length; i++) {
            const key = localStorage.key(i);
            const value = localStorage.getItem(key);
            
            if (value.length > 10000 && !this.isLikelyEncrypted(value)) {
                largeUnencrypted.push(key);
            }
        }
        
        if (largeUnencrypted.length > 0) {
            issues.push({
                severity: 'low',
                type: 'large-unencrypted-data',
                message: 'Large unencrypted data found',
                keys: largeUnencrypted,
                recommendation: 'Consider encrypting large data blobs'
            });
        }
        
        return {
            score: Math.max(0, score - (issues.filter(i => i.severity === 'high').length * 20)),
            issues
        };
    },
    
    // Helper functions
    detectDataType(value) {
        try {
            JSON.parse(value);
            return 'json';
        } catch (e) {
            if (this.looksLikeJWT(value)) return 'jwt';
            if (this.isLikelyEncrypted(value)) return 'encrypted';
            if (value.match(/^\d+$/)) return 'number';
            if (value.match(/^\d{4}-\d{2}-\d{2}/)) return 'date';
            return 'string';
        }
    },
    
    isLikelyEncrypted(value) {
        // Simple heuristic for encrypted data
        if (value.length < 20) return false;
        
        // Check for high entropy (lots of different characters)
        const uniqueChars = new Set(value).size;
        const entropy = uniqueChars / value.length;
        
        // Check for base64 pattern
        const base64Pattern = /^[A-Za-z0-9+/]+=*$/;
        
        return entropy > 0.7 || base64Pattern.test(value);
    },
    
    looksLikeJWT(value) {
        return /^[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+$/.test(value);
    },
    
    checkSensitiveData(text) {
        const patterns = [
            { name: 'email', regex: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/ },
            { name: 'phone', regex: /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/ },
            { name: 'ssn', regex: /\b\d{3}-\d{2}-\d{4}\b/ },
            { name: 'credit-card', regex: /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/ },
            { name: 'api-key', regex: /api[_-]?key[\s:="']+[\w\-]+/i },
            { name: 'password', regex: /password[\s:="']+[\w\-]+/i }
        ];
        
        const found = [];
        patterns.forEach(pattern => {
            if (pattern.regex.test(text)) {
                found.push(pattern.name);
            }
        });
        
        return found;
    },
    
    formatBytes(bytes) {
        if (bytes === 0) return '0 Bytes';
        const k = 1024;
        const sizes = ['Bytes', 'KB', 'MB', 'GB'];
        const i = Math.floor(Math.log(bytes) / Math.log(k));
        return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    },
    
    openDatabase(name) {
        return new Promise((resolve, reject) => {
            const request = indexedDB.open(name);
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    },
    
    promisify(request) {
        return new Promise((resolve, reject) => {
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    },
    
    generateSummary(analysis) {
        const summary = {
            totalItems: 0,
            totalSize: 0,
            byType: {}
        };
        
        // Count items and size
        Object.entries(analysis.details).forEach(([type, data]) => {
            if (data.count !== undefined) {
                summary.totalItems += data.count;
                summary.byType[type] = {
                    count: data.count,
                    size: data.totalSize || 0
                };
                summary.totalSize += data.totalSize || 0;
            }
        });
        
        summary.formattedSize = this.formatBytes(summary.totalSize);
        summary.securityScore = analysis.security.score;
        
        return summary;
    },
    
    generateRecommendations(analysis) {
        const recommendations = [];
        
        // Security recommendations
        if (analysis.security.issues.length > 0) {
            analysis.security.issues.forEach(issue => {
                if (issue.severity === 'high') {
                    recommendations.push({
                        priority: 'critical',
                        action: issue.recommendation,
                        reason: issue.message
                    });
                }
            });
        }
        
        // Storage optimization
        if (analysis.summary.totalSize > 5 * 1024 * 1024) { // 5MB
            recommendations.push({
                priority: 'medium',
                action: 'Implement storage cleanup strategy',
                reason: 'Large amount of data in browser storage'
            });
        }
        
        // Cookie recommendations
        if (analysis.details.cookies.count > 20) {
            recommendations.push({
                priority: 'low',
                action: 'Review and consolidate cookies',
                reason: 'High number of cookies can impact performance'
            });
        }
        
        return recommendations;
    }
};

// Run analysis
StorageAnalyzer.analyzeAllStorage().then(console.log);

Storage Security Scanner

Comprehensive security scanner for browser storage:

// Storage Security Scanner
const StorageSecurityScanner = {
    // Scan for security vulnerabilities
    async scanSecurity() {
        const results = {
            vulnerabilities: [],
            risks: {
                high: 0,
                medium: 0,
                low: 0
            },
            recommendations: [],
            score: 100
        };
        
        // Scan each storage type
        this.scanCookieSecurity(results);
        this.scanLocalStorageSecurity(results);
        this.scanSessionStorageSecurity(results);
        await this.scanIndexedDBSecurity(results);
        this.scanCrossOriginRisks(results);
        
        // Calculate final score
        results.score = Math.max(0, 100 - 
            (results.risks.high * 20) - 
            (results.risks.medium * 10) - 
            (results.risks.low * 5)
        );
        
        return results;
    },
    
    // Scan cookie security
    scanCookieSecurity(results) {
        const cookies = document.cookie.split(';').filter(c => c.trim());
        
        cookies.forEach(cookie => {
            const [name, value] = cookie.trim().split('=');
            
            // Check for session cookies without secure attributes
            if (name.toLowerCase().includes('session') || 
                name.toLowerCase().includes('auth')) {
                // Note: Can't check httpOnly or secure from JavaScript
                results.vulnerabilities.push({
                    type: 'insecure-session-cookie',
                    severity: 'high',
                    storage: 'cookie',
                    key: name,
                    issue: 'Session cookie may lack security attributes',
                    recommendation: 'Set httpOnly, secure, and sameSite attributes'
                });
                results.risks.high++;
            }
            
            // Check for sensitive data in cookies
            if (this.containsSensitiveData(value)) {
                results.vulnerabilities.push({
                    type: 'sensitive-data-in-cookie',
                    severity: 'medium',
                    storage: 'cookie',
                    key: name,
                    issue: 'Potentially sensitive data in cookie',
                    recommendation: 'Encrypt sensitive cookie values'
                });
                results.risks.medium++;
            }
        });
    },
    
    // Scan localStorage security
    scanLocalStorageSecurity(results) {
        for (let i = 0; i < localStorage.length; i++) {
            const key = localStorage.key(i);
            const value = localStorage.getItem(key);
            
            // Check for authentication tokens
            if (this.isAuthenticationToken(key, value)) {
                results.vulnerabilities.push({
                    type: 'auth-token-in-localstorage',
                    severity: 'high',
                    storage: 'localStorage',
                    key: key,
                    issue: 'Authentication token in localStorage is vulnerable to XSS',
                    recommendation: 'Use httpOnly cookies for auth tokens',
                    codeExample: `
// Instead of:
localStorage.setItem('authToken', token);

// Use httpOnly cookie:
// Set on server with httpOnly flag`
                });
                results.risks.high++;
            }
            
            // Check for unencrypted sensitive data
            if (this.containsSensitiveData(value) && !this.isEncrypted(value)) {
                results.vulnerabilities.push({
                    type: 'unencrypted-sensitive-data',
                    severity: 'medium',
                    storage: 'localStorage',
                    key: key,
                    issue: 'Sensitive data stored without encryption',
                    recommendation: 'Encrypt data before storing',
                    codeExample: `
// Use encryption:
const encrypted = await crypto.subtle.encrypt(
    algorithm,
    key,
    encoder.encode(sensitiveData)
);
localStorage.setItem('data', btoa(String.fromCharCode(...new Uint8Array(encrypted))));`
                });
                results.risks.medium++;
            }
            
            // Check for PII
            if (this.containsPII(value)) {
                results.vulnerabilities.push({
                    type: 'pii-in-storage',
                    severity: 'medium',
                    storage: 'localStorage',
                    key: key,
                    issue: 'Personally Identifiable Information in storage',
                    recommendation: 'Avoid storing PII or encrypt it'
                });
                results.risks.medium++;
            }
        }
    },
    
    // Scan sessionStorage security
    scanSessionStorageSecurity(results) {
        for (let i = 0; i < sessionStorage.length; i++) {
            const key = sessionStorage.key(i);
            const value = sessionStorage.getItem(key);
            
            // SessionStorage is better for sensitive data than localStorage
            // but still check for unencrypted sensitive data
            if (this.containsCreditCard(value) || this.containsSSN(value)) {
                results.vulnerabilities.push({
                    type: 'highly-sensitive-data',
                    severity: 'high',
                    storage: 'sessionStorage',
                    key: key,
                    issue: 'Highly sensitive data (CC/SSN) in storage',
                    recommendation: 'Never store credit cards or SSNs client-side'
                });
                results.risks.high++;
            }
        }
    },
    
    // Scan IndexedDB security
    async scanIndexedDBSecurity(results) {
        if (!window.indexedDB) return;
        
        try {
            const databases = await indexedDB.databases?.() || [];
            
            if (databases.length > 0) {
                // General IndexedDB security note
                results.vulnerabilities.push({
                    type: 'indexeddb-usage',
                    severity: 'low',
                    storage: 'IndexedDB',
                    issue: 'IndexedDB data is accessible to any script',
                    recommendation: 'Encrypt sensitive data in IndexedDB',
                    databases: databases.map(db => db.name)
                });
                results.risks.low++;
            }
        } catch (error) {
            // IndexedDB.databases() not supported
        }
    },
    
    // Scan cross-origin risks
    scanCrossOriginRisks(results) {
        // Check for cross-origin data
        const storageKeys = [];
        
        for (let i = 0; i < localStorage.length; i++) {
            storageKeys.push(localStorage.key(i));
        }
        
        // Look for keys that might indicate cross-origin data
        const crossOriginPatterns = ['iframe', 'embed', 'widget', 'third-party'];
        
        storageKeys.forEach(key => {
            if (crossOriginPatterns.some(pattern => key.toLowerCase().includes(pattern))) {
                results.vulnerabilities.push({
                    type: 'potential-cross-origin-data',
                    severity: 'low',
                    storage: 'localStorage',
                    key: key,
                    issue: 'Potential cross-origin data sharing',
                    recommendation: 'Validate origin before trusting data'
                });
                results.risks.low++;
            }
        });
    },
    
    // Helper methods
    isAuthenticationToken(key, value) {
        const tokenPatterns = ['token', 'auth', 'jwt', 'bearer', 'session'];
        const keyMatch = tokenPatterns.some(pattern => 
            key.toLowerCase().includes(pattern)
        );
        
        // Check if value looks like a token
        const isJWT = /^[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+$/.test(value);
        const isBearerToken = value.toLowerCase().startsWith('bearer ');
        const isLongRandomString = value.length > 20 && /^[A-Za-z0-9\-_]+$/.test(value);
        
        return keyMatch || isJWT || isBearerToken || isLongRandomString;
    },
    
    containsSensitiveData(value) {
        const patterns = [
            /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/, // Email
            /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/, // Phone
            /\b\d{3}-\d{2}-\d{4}\b/, // SSN
            /password/i,
            /api[_-]?key/i,
            /secret/i
        ];
        
        return patterns.some(pattern => pattern.test(value));
    },
    
    containsPII(value) {
        // Check for common PII patterns
        const piiPatterns = [
            /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/, // Email
            /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/, // Phone
            /\b\d{5}(?:[-\s]\d{4})?\b/, // ZIP code
            /\b(?:0[1-9]|1[0-2])\/(?:0[1-9]|[12]\d|3[01])\/(?:19|20)\d{2}\b/ // Birthdate
        ];
        
        return piiPatterns.some(pattern => pattern.test(value));
    },
    
    containsCreditCard(value) {
        // Basic credit card pattern
        const ccPattern = /\b(?:\d{4}[\s-]?){3}\d{4}\b/;
        return ccPattern.test(value);
    },
    
    containsSSN(value) {
        const ssnPattern = /\b\d{3}-\d{2}-\d{4}\b/;
        return ssnPattern.test(value);
    },
    
    isEncrypted(value) {
        // Simple heuristic for encrypted data
        if (value.length < 20) return false;
        
        // Check for high entropy
        const uniqueChars = new Set(value).size;
        const entropy = uniqueChars / value.length;
        
        // Check for base64 or hex patterns
        const base64Pattern = /^[A-Za-z0-9+/]+=*$/;
        const hexPattern = /^[0-9a-fA-F]+$/;
        
        return entropy > 0.7 || base64Pattern.test(value) || hexPattern.test(value);
    }
};

// Run security scan
StorageSecurityScanner.scanSecurity().then(console.log);

Storage Encryption Helper

Implement secure storage with encryption:

// Secure Storage Implementation
class SecureStorage {
    constructor() {
        this.algorithm = { name: 'AES-GCM', length: 256 };
        this.keyUsages = ['encrypt', 'decrypt'];
    }
    
    // Generate encryption key
    async generateKey() {
        return await crypto.subtle.generateKey(
            this.algorithm,
            true,
            this.keyUsages
        );
    }
    
    // Derive key from password
    async deriveKey(password, salt) {
        const encoder = new TextEncoder();
        const keyMaterial = await crypto.subtle.importKey(
            'raw',
            encoder.encode(password),
            'PBKDF2',
            false,
            ['deriveBits', 'deriveKey']
        );
        
        return await crypto.subtle.deriveKey(
            {
                name: 'PBKDF2',
                salt: salt,
                iterations: 100000,
                hash: 'SHA-256'
            },
            keyMaterial,
            this.algorithm,
            true,
            this.keyUsages
        );
    }
    
    // Encrypt data
    async encrypt(data, key) {
        const encoder = new TextEncoder();
        const iv = crypto.getRandomValues(new Uint8Array(12));
        
        const encrypted = await crypto.subtle.encrypt(
            {
                name: 'AES-GCM',
                iv: iv
            },
            key,
            encoder.encode(JSON.stringify(data))
        );
        
        // Combine iv and encrypted data
        const combined = new Uint8Array(iv.length + encrypted.byteLength);
        combined.set(iv);
        combined.set(new Uint8Array(encrypted), iv.length);
        
        // Convert to base64 for storage
        return btoa(String.fromCharCode(...combined));
    }
    
    // Decrypt data
    async decrypt(encryptedData, key) {
        // Convert from base64
        const combined = Uint8Array.from(atob(encryptedData), c => c.charCodeAt(0));
        
        // Extract iv and encrypted data
        const iv = combined.slice(0, 12);
        const encrypted = combined.slice(12);
        
        const decrypted = await crypto.subtle.decrypt(
            {
                name: 'AES-GCM',
                iv: iv
            },
            key,
            encrypted
        );
        
        const decoder = new TextDecoder();
        return JSON.parse(decoder.decode(decrypted));
    }
    
    // Secure storage methods
    async setSecure(key, value, password) {
        try {
            // Generate salt for this item
            const salt = crypto.getRandomValues(new Uint8Array(16));
            const cryptoKey = await this.deriveKey(password, salt);
            
            const encrypted = await this.encrypt(value, cryptoKey);
            
            // Store encrypted data with salt
            const storageItem = {
                data: encrypted,
                salt: btoa(String.fromCharCode(...salt))
            };
            
            localStorage.setItem(key, JSON.stringify(storageItem));
            return true;
        } catch (error) {
            console.error('Encryption failed:', error);
            return false;
        }
    }
    
    async getSecure(key, password) {
        try {
            const stored = localStorage.getItem(key);
            if (!stored) return null;
            
            const storageItem = JSON.parse(stored);
            const salt = Uint8Array.from(atob(storageItem.salt), c => c.charCodeAt(0));
            
            const cryptoKey = await this.deriveKey(password, salt);
            return await this.decrypt(storageItem.data, cryptoKey);
        } catch (error) {
            console.error('Decryption failed:', error);
            return null;
        }
    }
    
    // Session-based secure storage
    async setSecureSession(key, value) {
        // Generate a session key
        if (!this.sessionKey) {
            this.sessionKey = await this.generateKey();
        }
        
        const encrypted = await this.encrypt(value, this.sessionKey);
        sessionStorage.setItem(key, encrypted);
    }
    
    async getSecureSession(key) {
        if (!this.sessionKey) return null;
        
        const encrypted = sessionStorage.getItem(key);
        if (!encrypted) return null;
        
        return await this.decrypt(encrypted, this.sessionKey);
    }
}

// Usage example
const secureStorage = new SecureStorage();

// Store sensitive data securely
async function storeSensitiveData() {
    const sensitiveData = {
        apiKey: 'secret-api-key',
        userPreferences: { theme: 'dark', language: 'en' }
    };
    
    await secureStorage.setSecure('userData', sensitiveData, 'user-password');
    
    // Retrieve data
    const retrieved = await secureStorage.getSecure('userData', 'user-password');
    console.log('Retrieved:', retrieved);
}

Storage Quota Manager

Manage storage quotas effectively:

// Storage Quota Manager
class StorageQuotaManager {
    // Check storage quota
    async checkQuota() {
        const quota = {
            available: false,
            usage: 0,
            quota: 0,
            percent: 0,
            breakdown: {}
        };
        
        if ('storage' in navigator && 'estimate' in navigator.storage) {
            try {
                const estimate = await navigator.storage.estimate();
                quota.available = true;
                quota.usage = estimate.usage;
                quota.quota = estimate.quota;
                quota.percent = (estimate.usage / estimate.quota * 100).toFixed(2);
                
                // Try to get breakdown by storage type
                if (estimate.usageDetails) {
                    quota.breakdown = estimate.usageDetails;
                }
            } catch (error) {
                quota.error = error.message;
            }
        }
        
        // Add manual calculations
        quota.breakdown.localStorage = this.calculateLocalStorageSize();
        quota.breakdown.sessionStorage = this.calculateSessionStorageSize();
        quota.breakdown.cookies = document.cookie.length;
        
        return quota;
    }
    
    // Calculate localStorage size
    calculateLocalStorageSize() {
        let size = 0;
        for (let key in localStorage) {
            if (localStorage.hasOwnProperty(key)) {
                size += localStorage[key].length + key.length;
            }
        }
        return size;
    }
    
    // Calculate sessionStorage size
    calculateSessionStorageSize() {
        let size = 0;
        for (let key in sessionStorage) {
            if (sessionStorage.hasOwnProperty(key)) {
                size += sessionStorage[key].length + key.length;
            }
        }
        return size;
    }
    
    // Request persistent storage
    async requestPersistence() {
        if ('storage' in navigator && 'persist' in navigator.storage) {
            try {
                const isPersisted = await navigator.storage.persisted();
                
                if (!isPersisted) {
                    const granted = await navigator.storage.persist();
                    return {
                        granted,
                        message: granted ? 
                            'Persistent storage granted' : 
                            'Persistent storage denied'
                    };
                }
                
                return {
                    granted: true,
                    message: 'Storage is already persistent'
                };
            } catch (error) {
                return {
                    granted: false,
                    error: error.message
                };
            }
        }
        
        return {
            granted: false,
            error: 'Persistent storage API not supported'
        };
    }
    
    // Clean up storage
    async cleanup(options = {}) {
        const report = {
            cleared: [],
            errors: [],
            spaceSaved: 0
        };
        
        // Clear old items from localStorage
        if (options.localStorage) {
            const maxAge = options.maxAge || 30 * 24 * 60 * 60 * 1000; // 30 days
            
            for (let i = localStorage.length - 1; i >= 0; i--) {
                const key = localStorage.key(i);
                
                try {
                    const value = localStorage.getItem(key);
                    const data = JSON.parse(value);
                    
                    if (data.timestamp && Date.now() - data.timestamp > maxAge) {
                        report.spaceSaved += key.length + value.length;
                        localStorage.removeItem(key);
                        report.cleared.push({ type: 'localStorage', key });
                    }
                } catch (e) {
                    // Not JSON or no timestamp, skip
                }
            }
        }
        
        // Clear expired cookies
        if (options.cookies) {
            const cookies = document.cookie.split(';');
            cookies.forEach(cookie => {
                const [name] = cookie.trim().split('=');
                // Set cookie with past expiration to delete
                document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/`;
            });
        }
        
        // Clear cache storage
        if (options.cache && 'caches' in window) {
            try {
                const cacheNames = await caches.keys();
                for (const name of cacheNames) {
                    if (options.cacheWhitelist && !options.cacheWhitelist.includes(name)) {
                        await caches.delete(name);
                        report.cleared.push({ type: 'cache', name });
                    }
                }
            } catch (error) {
                report.errors.push({ type: 'cache', error: error.message });
            }
        }
        
        return report;
    }
    
    // Monitor storage events
    monitorStorage(callback) {
        // Listen for storage events (only fires for other tabs)
        window.addEventListener('storage', (event) => {
            callback({
                type: 'storage',
                key: event.key,
                oldValue: event.oldValue,
                newValue: event.newValue,
                url: event.url,
                storageArea: event.storageArea === localStorage ? 'localStorage' : 'sessionStorage'
            });
        });
        
        // Override localStorage methods to monitor same-tab changes
        const originalSetItem = localStorage.setItem;
        localStorage.setItem = function(key, value) {
            const oldValue = localStorage.getItem(key);
            originalSetItem.call(localStorage, key, value);
            
            callback({
                type: 'setItem',
                key,
                oldValue,
                newValue: value,
                storageArea: 'localStorage'
            });
        };
        
        const originalRemoveItem = localStorage.removeItem;
        localStorage.removeItem = function(key) {
            const oldValue = localStorage.getItem(key);
            originalRemoveItem.call(localStorage, key);
            
            callback({
                type: 'removeItem',
                key,
                oldValue,
                newValue: null,
                storageArea: 'localStorage'
            });
        };
        
        return {
            stop: () => {
                window.removeEventListener('storage', callback);
                localStorage.setItem = originalSetItem;
                localStorage.removeItem = originalRemoveItem;
            }
        };
    }
}

// Usage
const quotaManager = new StorageQuotaManager();
quotaManager.checkQuota().then(console.log);

Best Practices for Browser Storage

  1. Never Store Sensitive Data Unencrypted: Always encrypt PII and auth tokens
  2. Use the Right Storage Type: Session > Local > Cookies for sensitivity
  3. Implement Expiration: Add timestamps and clean up old data
  4. Handle Quota Errors: Always try-catch storage operations
  5. Validate Storage Data: Never trust data from storage without validation
  6. Use HTTP-Only Cookies: For authentication tokens
  7. Implement CSP: Prevent XSS attacks that could access storage
  8. Monitor Storage Usage: Track quota to prevent failures
  9. Clear on Logout: Remove all sensitive data on user logout
  10. Test Cross-Browser: Storage limits vary significantly

Common Storage Security Issues

  • XSS Access: Any script can read localStorage/sessionStorage
  • No Encryption: Data stored in plain text by default
  • Quota Exceeded: Failing to handle storage limits
  • Stale Data: Not implementing expiration/cleanup
  • Cross-Tab Sync Issues: Storage events don't fire in same tab
  • Third-Party Access: Embedded content accessing storage
  • Network Interception: Unencrypted data in cookies
  • Browser Extensions: Can access all storage
  • Physical Access: Storage persists on disk
  • Cross-Origin Leaks: Improper origin validation

Remember: Browser storage is convenient but not secure by default. Always assume storage can be accessed by malicious scripts and plan accordingly. Encrypt sensitive data, validate everything, and implement proper cleanup strategies.