Chapter 12 Security and Permission Management
Haiyue
48min
Chapter 12: Security and Permission Management
Learning Objectives
- Master Chrome extension security architecture and threat models
- Learn to properly configure and manage permissions
- Implement secure data handling and communication mechanisms
- Prevent common security attacks and vulnerabilities
12.1 Security Architecture Overview
12.1.1 Chrome Extension Security Model
🔄 正在渲染 Mermaid 图表...
12.1.2 Security Threat Analysis
// security-analyzer.js - Security threat analyzer
class SecurityAnalyzer {
constructor() {
this.threats = new Map();
this.vulnerabilities = new Set();
this.securityPolicies = new Map();
this.auditLog = [];
this.init();
}
init() {
this.setupThreatDetection();
this.setupVulnerabilityScanning();
this.setupSecurityPolicies();
this.startSecurityMonitoring();
}
// Threat model definition
defineThreatModel() {
return {
// Data exfiltration threat
dataExfiltration: {
description: 'Sensitive data is maliciously accessed or transmitted',
likelihood: 'medium',
impact: 'high',
mitigations: [
'Data encryption',
'Access control',
'Audit logging',
'Principle of least privilege'
]
},
// Code injection threat
codeInjection: {
description: 'Malicious code is injected into the extension',
likelihood: 'medium',
impact: 'critical',
mitigations: [
'CSP policy',
'Input validation',
'Code review',
'Sandbox execution'
]
},
// Privilege escalation threat
privilegeEscalation: {
description: 'Extension permissions are misused',
likelihood: 'low',
impact: 'high',
mitigations: [
'Minimize permissions',
'Dynamic permissions',
'User authorization',
'Permission auditing'
]
},
// Cross-site scripting attack
xssAttack: {
description: 'Malicious scripts execute on web pages',
likelihood: 'high',
impact: 'medium',
mitigations: [
'Output encoding',
'Input filtering',
'CSP protection',
'Domain validation'
]
},
// Man-in-the-middle attack
mitm: {
description: 'Network communication is intercepted or tampered with',
likelihood: 'low',
impact: 'high',
mitigations: [
'HTTPS enforcement',
'Certificate validation',
'Request signing',
'Communication encryption'
]
}
};
}
setupThreatDetection() {
// Detect suspicious network requests
this.monitorNetworkRequests();
// Detect abnormal permission usage
this.monitorPermissionUsage();
// Detect potential XSS attacks
this.monitorXSSAttempts();
// Detect data leakage risks
this.monitorDataAccess();
}
monitorNetworkRequests() {
// Wrap fetch and XMLHttpRequest
const originalFetch = window.fetch;
window.fetch = (...args) => {
this.analyzeRequest('fetch', args);
return originalFetch.apply(this, args);
};
const originalXHROpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(...args) {
this.analyzeRequest('xhr', args);
return originalXHROpen.apply(this, args);
}.bind(this);
}
analyzeRequest(type, args) {
const url = args[0] || args[1];
if (typeof url === 'string') {
// Check for suspicious domains
if (this.isSuspiciousDomain(url)) {
this.reportThreat('suspicious_domain', {
url,
type,
timestamp: Date.now()
});
}
// Check if using HTTP (should use HTTPS)
if (url.startsWith('http://') && !url.includes('localhost')) {
this.reportThreat('insecure_connection', {
url,
type,
timestamp: Date.now()
});
}
// Check if carrying sensitive data
if (this.containsSensitiveData(args)) {
this.reportThreat('sensitive_data_transmission', {
url,
type,
timestamp: Date.now()
});
}
}
}
monitorPermissionUsage() {
// Wrap chrome API calls
const originalTabsQuery = chrome.tabs.query;
chrome.tabs.query = (...args) => {
this.auditAPIUsage('tabs.query', args);
return originalTabsQuery.apply(chrome.tabs, args);
};
const originalStorageGet = chrome.storage.sync.get;
chrome.storage.sync.get = (...args) => {
this.auditAPIUsage('storage.sync.get', args);
return originalStorageGet.apply(chrome.storage.sync, args);
};
}
auditAPIUsage(api, args) {
const usage = {
api,
args: this.sanitizeArgs(args),
timestamp: Date.now(),
callStack: new Error().stack
};
this.auditLog.push(usage);
// Check for abnormally frequent API calls
const recentUsage = this.auditLog.filter(log =>
log.api === api &&
Date.now() - log.timestamp < 60000 // Last minute
);
if (recentUsage.length > 100) {
this.reportThreat('excessive_api_usage', {
api,
count: recentUsage.length,
timestamp: Date.now()
});
}
}
monitorXSSAttempts() {
// Monitor DOM insertion operations
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
this.scanForXSS(node);
}
});
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
scanForXSS(element) {
const suspiciousPatterns = [
/<script[^>]*>.*<\/script>/gi,
/javascript:/gi,
/on\w+\s*=/gi,
/<iframe[^>]*>/gi
];
const innerHTML = element.innerHTML;
suspiciousPatterns.forEach(pattern => {
if (pattern.test(innerHTML)) {
this.reportThreat('potential_xss', {
element: element.tagName,
content: innerHTML.substring(0, 200),
timestamp: Date.now()
});
}
});
}
monitorDataAccess() {
// Monitor localStorage access
const originalSetItem = localStorage.setItem;
localStorage.setItem = function(key, value) {
this.auditDataAccess('localStorage.setItem', key, value);
return originalSetItem.call(localStorage, key, value);
}.bind(this);
const originalGetItem = localStorage.getItem;
localStorage.getItem = function(key) {
this.auditDataAccess('localStorage.getItem', key);
return originalGetItem.call(localStorage, key);
}.bind(this);
}
auditDataAccess(operation, key, value = null) {
// Check if accessing sensitive data
if (this.isSensitiveKey(key)) {
this.auditLog.push({
operation,
key,
hasSensitiveValue: value && this.isSensitiveValue(value),
timestamp: Date.now()
});
}
}
isSuspiciousDomain(url) {
const suspiciousDomains = [
'malicious-site.com',
'phishing-domain.net',
'suspicious-tracker.org'
];
try {
const domain = new URL(url).hostname;
return suspiciousDomains.some(suspiciousDomain =>
domain.includes(suspiciousDomain)
);
} catch {
return false;
}
}
containsSensitiveData(requestArgs) {
const requestBody = JSON.stringify(requestArgs);
const sensitivePatterns = [
/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g, // Credit card numbers
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, // Email addresses
/\b\d{11}\b/g, // Phone numbers
/password|token|secret|key/gi // Sensitive keywords
];
return sensitivePatterns.some(pattern => pattern.test(requestBody));
}
isSensitiveKey(key) {
const sensitiveKeys = [
'password', 'token', 'secret', 'apiKey', 'privateKey',
'creditCard', 'ssn', 'personalData', 'authToken'
];
return sensitiveKeys.some(sensitiveKey =>
key.toLowerCase().includes(sensitiveKey.toLowerCase())
);
}
isSensitiveValue(value) {
if (typeof value !== 'string') return false;
// Check if looks like a password
if (value.length > 8 && /[A-Z]/.test(value) && /[0-9]/.test(value)) {
return true;
}
// Check if looks like a token
if (value.length > 32 && /^[A-Za-z0-9+/]+=*$/.test(value)) {
return true;
}
return false;
}
reportThreat(threatType, details) {
const threat = {
type: threatType,
details,
severity: this.calculateSeverity(threatType),
timestamp: Date.now(),
resolved: false
};
this.threats.set(`${threatType}_${Date.now()}`, threat);
console.warn('Security threat detected:', threat);
// Send to security monitoring service (if available)
this.sendSecurityAlert(threat);
}
calculateSeverity(threatType) {
const severityMap = {
'suspicious_domain': 'high',
'insecure_connection': 'medium',
'sensitive_data_transmission': 'high',
'excessive_api_usage': 'medium',
'potential_xss': 'high',
'unauthorized_access': 'critical'
};
return severityMap[threatType] || 'low';
}
sendSecurityAlert(threat) {
// This can send to a security monitoring service
// Or log to local storage
chrome.runtime.sendMessage({
action: 'securityAlert',
threat: threat
});
}
sanitizeArgs(args) {
// Remove sensitive information
return args.map(arg => {
if (typeof arg === 'string' && this.isSensitiveValue(arg)) {
return '[SENSITIVE_DATA]';
}
if (typeof arg === 'object' && arg !== null) {
const sanitized = {};
Object.keys(arg).forEach(key => {
sanitized[key] = this.isSensitiveKey(key) ? '[SENSITIVE_DATA]' : arg[key];
});
return sanitized;
}
return arg;
});
}
setupVulnerabilityScanning() {
// Periodically scan for known vulnerabilities
setInterval(() => {
this.scanForVulnerabilities();
}, 300000); // Every 5 minutes
}
scanForVulnerabilities() {
// Check dependency security issues
this.checkDependencyVulnerabilities();
// Check configuration security issues
this.checkConfigurationSecurity();
// Check permission usage issues
this.checkPermissionSecurity();
}
checkDependencyVulnerabilities() {
// Check if using libraries with known vulnerabilities
const knownVulnerableLibraries = [
'jquery@1.x.x',
'lodash@4.17.11',
'axios@0.18.0'
];
// Should check actually loaded libraries
// Simplified example
knownVulnerableLibraries.forEach(lib => {
if (this.isLibraryLoaded(lib)) {
this.vulnerabilities.add(`vulnerable_dependency: ${lib}`);
}
});
}
checkConfigurationSecurity() {
// Check CSP configuration
const csp = document.querySelector('meta[http-equiv="Content-Security-Policy"]');
if (!csp || !this.isSecureCSP(csp.content)) {
this.vulnerabilities.add('weak_csp');
}
// Check if using HTTPS
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
this.vulnerabilities.add('insecure_protocol');
}
}
checkPermissionSecurity() {
// Check if using excessive permissions
chrome.management.getSelf((extensionInfo) => {
const permissions = extensionInfo.permissions;
const excessivePermissions = [
'tabs', 'history', 'bookmarks'
];
const hasExcessive = permissions.some(permission =>
excessivePermissions.includes(permission)
);
if (hasExcessive) {
this.vulnerabilities.add('excessive_permissions');
}
});
}
isLibraryLoaded(libraryPattern) {
// Simplified check, should be more complex in practice
return false;
}
isSecureCSP(cspContent) {
if (!cspContent) return false;
// Check if unsafe inline scripts are prohibited
if (cspContent.includes("'unsafe-inline'")) return false;
// Check if eval is prohibited
if (cspContent.includes("'unsafe-eval'")) return false;
// Check if there are appropriate source restrictions
if (!cspContent.includes('self') && !cspContent.includes('none')) return false;
return true;
}
setupSecurityPolicies() {
this.securityPolicies.set('dataRetention', {
maxAge: 90 * 24 * 60 * 60 * 1000, // 90 days
sensitiveDataMaxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
cleanupInterval: 24 * 60 * 60 * 1000 // Daily cleanup
});
this.securityPolicies.set('networkSecurity', {
allowedDomains: ['*.example.com', 'api.service.com'],
requireHTTPS: true,
maxRequestSize: 1024 * 1024, // 1MB
timeoutMs: 30000
});
this.securityPolicies.set('accessControl', {
maxLoginAttempts: 3,
lockoutDurationMs: 15 * 60 * 1000, // 15 minutes
sessionTimeoutMs: 2 * 60 * 60 * 1000, // 2 hours
requireReauthentication: true
});
}
startSecurityMonitoring() {
// Periodically clean sensitive data
setInterval(() => {
this.cleanupSensitiveData();
}, 24 * 60 * 60 * 1000); // Daily
// Monitor abnormal behavior
setInterval(() => {
this.analyzeUserBehavior();
}, 60 * 1000); // Every minute
}
cleanupSensitiveData() {
const policy = this.securityPolicies.get('dataRetention');
const now = Date.now();
// Clean expired audit logs
this.auditLog = this.auditLog.filter(log =>
now - log.timestamp < policy.maxAge
);
// Clean expired threat records
for (const [key, threat] of this.threats.entries()) {
if (now - threat.timestamp > policy.maxAge) {
this.threats.delete(key);
}
}
}
analyzeUserBehavior() {
// Analyze recent activity patterns
const recentActivity = this.auditLog.filter(log =>
Date.now() - log.timestamp < 5 * 60 * 1000 // Last 5 minutes
);
// Check for abnormal activity
if (recentActivity.length > 50) {
this.reportThreat('unusual_activity', {
activityCount: recentActivity.length,
timeWindow: '5 minutes'
});
}
// Check permission usage patterns
const apiUsage = {};
recentActivity.forEach(log => {
if (log.api) {
apiUsage[log.api] = (apiUsage[log.api] || 0) + 1;
}
});
Object.entries(apiUsage).forEach(([api, count]) => {
if (count > 20) {
this.reportThreat('excessive_api_usage', {
api,
count,
timeWindow: '5 minutes'
});
}
});
}
// Get security report
getSecurityReport() {
const now = Date.now();
const oneDayAgo = now - 24 * 60 * 60 * 1000;
const recentThreats = Array.from(this.threats.values())
.filter(threat => threat.timestamp > oneDayAgo);
const threatsByType = {};
recentThreats.forEach(threat => {
threatsByType[threat.type] = (threatsByType[threat.type] || 0) + 1;
});
const criticalThreats = recentThreats.filter(threat =>
threat.severity === 'critical'
);
const highThreats = recentThreats.filter(threat =>
threat.severity === 'high'
);
return {
summary: {
totalThreats: recentThreats.length,
criticalThreats: criticalThreats.length,
highThreats: highThreats.length,
vulnerabilities: Array.from(this.vulnerabilities),
securityScore: this.calculateSecurityScore()
},
threatsByType,
recentThreats: recentThreats.slice(0, 10), // Last 10 threats
auditStats: {
totalEvents: this.auditLog.length,
recentEvents: this.auditLog.filter(log =>
log.timestamp > oneDayAgo
).length
}
};
}
calculateSecurityScore() {
let score = 100;
// Deduct points based on threat count
const threats = Array.from(this.threats.values());
const criticalThreats = threats.filter(t => t.severity === 'critical');
const highThreats = threats.filter(t => t.severity === 'high');
const mediumThreats = threats.filter(t => t.severity === 'medium');
score -= criticalThreats.length * 25;
score -= highThreats.length * 10;
score -= mediumThreats.length * 5;
// Deduct points based on vulnerability count
score -= this.vulnerabilities.size * 15;
return Math.max(0, Math.min(100, score));
}
}
// Global security analyzer instance
const securityAnalyzer = new SecurityAnalyzer();
// Export for use by other modules
window.securityAnalyzer = securityAnalyzer;
12.2 Permission Management
12.2.1 Dynamic Permission System
// permission-manager.js - Permission manager
class PermissionManager {
constructor() {
this.requiredPermissions = new Set();
this.optionalPermissions = new Set();
this.grantedPermissions = new Set();
this.permissionRequests = new Map();
this.permissionUsage = new Map();
this.init();
}
async init() {
await this.loadCurrentPermissions();
this.setupPermissionMonitoring();
this.setupPermissionPolicies();
}
async loadCurrentPermissions() {
return new Promise((resolve) => {
chrome.permissions.getAll((permissions) => {
permissions.permissions.forEach(permission => {
this.grantedPermissions.add(permission);
});
permissions.origins.forEach(origin => {
this.grantedPermissions.add(origin);
});
console.log('Currently granted permissions:', Array.from(this.grantedPermissions));
resolve();
});
});
}
// Permission classification management
definePermissions() {
// Required permissions (declared in manifest)
this.requiredPermissions = new Set([
'storage',
'activeTab'
]);
// Optional permissions (requested on demand)
this.optionalPermissions = new Set([
'tabs',
'history',
'bookmarks',
'downloads',
'notifications',
'contextMenus',
'https://*.example.com/*',
'https://api.service.com/*'
]);
}
// Request permission
async requestPermission(permission, justification = '') {
if (this.grantedPermissions.has(permission)) {
return { granted: true, alreadyGranted: true };
}
if (!this.optionalPermissions.has(permission)) {
return {
granted: false,
error: 'Permission is not in the optional permissions list'
};
}
const requestId = this.generateRequestId();
const request = {
id: requestId,
permission,
justification,
timestamp: Date.now(),
status: 'pending'
};
this.permissionRequests.set(requestId, request);
try {
// Show permission request dialog
const userApproved = await this.showPermissionDialog(permission, justification);
if (!userApproved) {
request.status = 'denied';
return { granted: false, userDenied: true };
}
// Request Chrome permission
const granted = await this.requestChromePermission(permission);
if (granted) {
this.grantedPermissions.add(permission);
request.status = 'granted';
this.logPermissionUsage(permission, 'granted');
return { granted: true, newlyGranted: true };
} else {
request.status = 'denied';
return { granted: false, chromeDenied: true };
}
} catch (error) {
request.status = 'error';
request.error = error.message;
return { granted: false, error: error.message };
}
}
async showPermissionDialog(permission, justification) {
return new Promise((resolve) => {
const modal = this.createPermissionModal(permission, justification);
const approveBtn = modal.querySelector('.approve-btn');
const denyBtn = modal.querySelector('.deny-btn');
approveBtn.addEventListener('click', () => {
document.body.removeChild(modal);
resolve(true);
});
denyBtn.addEventListener('click', () => {
document.body.removeChild(modal);
resolve(false);
});
document.body.appendChild(modal);
// Auto-focus on deny button (safer default choice)
denyBtn.focus();
});
}
createPermissionModal(permission, justification) {
const modal = document.createElement('div');
modal.className = 'permission-modal-overlay';
const permissionInfo = this.getPermissionInfo(permission);
modal.innerHTML = `
<div class="permission-modal">
<div class="modal-header">
<h3>Permission Request</h3>
<span class="security-badge">${permissionInfo.riskLevel}</span>
</div>
<div class="modal-body">
<div class="permission-icon">
<span>${permissionInfo.icon}</span>
</div>
<h4>${permissionInfo.name}</h4>
<p class="permission-description">${permissionInfo.description}</p>
${justification ? `
<div class="justification">
<h5>Why this permission is needed:</h5>
<p>${justification}</p>
</div>
` : ''}
<div class="risk-info">
<h5>Risk Warning:</h5>
<ul>
${permissionInfo.risks.map(risk => `<li>${risk}</li>`).join('')}
</ul>
</div>
<div class="usage-info">
<h5>This permission will be used for:</h5>
<ul>
${permissionInfo.usages.map(usage => `<li>${usage}</li>`).join('')}
</ul>
</div>
</div>
<div class="modal-footer">
<button class="btn deny-btn">Deny</button>
<button class="btn approve-btn">Grant</button>
</div>
</div>
`;
return modal;
}
getPermissionInfo(permission) {
const permissionMap = {
'tabs': {
name: 'Tab Access',
icon: '🗂️',
riskLevel: 'high',
description: 'Allows the extension to view and control browser tabs',
risks: [
'Can access all tab URLs and titles',
'Can close or modify tabs',
'May leak browsing history'
],
usages: [
'Manage tabs',
'Sync tab states',
'Provide tab-related features'
]
},
'history': {
name: 'Browsing History',
icon: '📚',
riskLevel: 'high',
description: 'Allows the extension to access browsing history',
risks: [
'Can view complete browsing history',
'May leak privacy information',
'Can delete history records'
],
usages: [
'Analyze browsing habits',
'Provide history search',
'Generate statistics reports'
]
},
'bookmarks': {
name: 'Bookmark Management',
icon: '🔖',
riskLevel: 'medium',
description: 'Allows the extension to access and manage bookmarks',
risks: [
'Can view all bookmarks',
'Can modify or delete bookmarks',
'May leak personal preferences'
],
usages: [
'Sync bookmarks',
'Manage bookmarks',
'Import/export bookmarks'
]
},
'downloads': {
name: 'Download Management',
icon: '💾',
riskLevel: 'medium',
description: 'Allows the extension to manage download items',
risks: [
'Can view download history',
'Can control download behavior',
'Can access downloaded files'
],
usages: [
'Manage download queue',
'Provide download enhancements',
'Monitor download progress'
]
},
'notifications': {
name: 'Desktop Notifications',
icon: '🔔',
riskLevel: 'low',
description: 'Allows the extension to display desktop notifications',
risks: [
'May display disruptive notifications',
'May be used for spam'
],
usages: [
'Important event reminders',
'Status update notifications',
'User interaction feedback'
]
}
};
return permissionMap[permission] || {
name: permission,
icon: '⚠️',
riskLevel: 'unknown',
description: `${permission} permission`,
risks: ['Unknown risk'],
usages: ['Unknown usage']
};
}
async requestChromePermission(permission) {
return new Promise((resolve) => {
const permissionObject = {
permissions: [],
origins: []
};
if (permission.includes('://')) {
permissionObject.origins.push(permission);
} else {
permissionObject.permissions.push(permission);
}
chrome.permissions.request(permissionObject, (granted) => {
resolve(granted);
});
});
}
// Revoke permission
async revokePermission(permission) {
if (!this.grantedPermissions.has(permission)) {
return { revoked: false, notGranted: true };
}
try {
const permissionObject = {
permissions: [],
origins: []
};
if (permission.includes('://')) {
permissionObject.origins.push(permission);
} else {
permissionObject.permissions.push(permission);
}
const revoked = await new Promise((resolve) => {
chrome.permissions.remove(permissionObject, resolve);
});
if (revoked) {
this.grantedPermissions.delete(permission);
this.logPermissionUsage(permission, 'revoked');
return { revoked: true };
} else {
return { revoked: false, error: 'Chrome permission revocation failed' };
}
} catch (error) {
return { revoked: false, error: error.message };
}
}
// Check permission
hasPermission(permission) {
return this.grantedPermissions.has(permission);
}
async checkPermission(permission) {
return new Promise((resolve) => {
const permissionObject = {
permissions: [],
origins: []
};
if (permission.includes('://')) {
permissionObject.origins.push(permission);
} else {
permissionObject.permissions.push(permission);
}
chrome.permissions.contains(permissionObject, resolve);
});
}
// Batch permission management
async requestMultiplePermissions(permissions, justifications = {}) {
const results = {};
for (const permission of permissions) {
const justification = justifications[permission] || '';
results[permission] = await this.requestPermission(permission, justification);
// If user denied permission, ask if should continue
if (!results[permission].granted && results[permission].userDenied) {
const shouldContinue = confirm('Some permissions were denied. Continue requesting other permissions?');
if (!shouldContinue) break;
}
}
return results;
}
// Permission usage monitoring
setupPermissionMonitoring() {
chrome.permissions.onAdded.addListener((permissions) => {
permissions.permissions.forEach(permission => {
this.grantedPermissions.add(permission);
console.log('Permission added:', permission);
});
permissions.origins.forEach(origin => {
this.grantedPermissions.add(origin);
console.log('Permission added:', origin);
});
});
chrome.permissions.onRemoved.addListener((permissions) => {
permissions.permissions.forEach(permission => {
this.grantedPermissions.delete(permission);
console.log('Permission removed:', permission);
});
permissions.origins.forEach(origin => {
this.grantedPermissions.delete(origin);
console.log('Permission removed:', origin);
});
});
}
logPermissionUsage(permission, action, details = {}) {
const usage = {
permission,
action,
details,
timestamp: Date.now()
};
if (!this.permissionUsage.has(permission)) {
this.permissionUsage.set(permission, []);
}
this.permissionUsage.get(permission).push(usage);
// Limit log size
const logs = this.permissionUsage.get(permission);
if (logs.length > 100) {
logs.splice(0, logs.length - 50);
}
}
setupPermissionPolicies() {
// Set permission usage policies
this.policies = {
// Auto-revoke policy
autoRevoke: {
enabled: true,
unusedDays: 30, // Auto-revoke after 30 days of non-use
excludePermissions: ['storage', 'activeTab'] // Exclude basic permissions
},
// Permission usage warning policy
usageWarning: {
enabled: true,
excessiveUsageThreshold: 100, // More than 100 uses in one hour
warningCooldown: 24 * 60 * 60 * 1000 // Don't repeat warning within 24 hours
},
// Sensitive permission policy
sensitivePermissions: {
permissions: ['history', 'bookmarks', 'downloads'],
requireReconfirmation: true,
reconfirmationInterval: 7 * 24 * 60 * 60 * 1000 // Reconfirm every 7 days
}
};
// Start policy enforcement
this.startPolicyEnforcement();
}
startPolicyEnforcement() {
// Check permission usage daily
setInterval(() => {
this.enforcePermissionPolicies();
}, 24 * 60 * 60 * 1000);
// Check usage patterns hourly
setInterval(() => {
this.checkUsagePatterns();
}, 60 * 60 * 1000);
}
enforcePermissionPolicies() {
if (this.policies.autoRevoke.enabled) {
this.revokeUnusedPermissions();
}
if (this.policies.sensitivePermissions.requireReconfirmation) {
this.checkSensitivePermissionReconfirmation();
}
}
revokeUnusedPermissions() {
const cutoffTime = Date.now() - (this.policies.autoRevoke.unusedDays * 24 * 60 * 60 * 1000);
this.grantedPermissions.forEach(permission => {
if (this.policies.autoRevoke.excludePermissions.includes(permission)) {
return;
}
const usageLogs = this.permissionUsage.get(permission) || [];
const recentUsage = usageLogs.filter(log =>
log.timestamp > cutoffTime && log.action === 'used'
);
if (recentUsage.length === 0) {
console.log(`Permission ${permission} has not been used for ${this.policies.autoRevoke.unusedDays} days, consider revoking`);
// Can choose to auto-revoke or notify user here
this.promptPermissionRevoke(permission);
}
});
}
promptPermissionRevoke(permission) {
const shouldRevoke = confirm(
`Permission "${permission}" has not been used for a long time. Revoke this permission?\n\n` +
'You can request it again when needed.'
);
if (shouldRevoke) {
this.revokePermission(permission);
}
}
checkUsagePatterns() {
const oneHourAgo = Date.now() - 60 * 60 * 1000;
this.permissionUsage.forEach((logs, permission) => {
const recentUsage = logs.filter(log =>
log.timestamp > oneHourAgo && log.action === 'used'
);
if (recentUsage.length > this.policies.usageWarning.excessiveUsageThreshold) {
this.warnExcessiveUsage(permission, recentUsage.length);
}
});
}
warnExcessiveUsage(permission, count) {
console.warn(`Permission ${permission} was used ${count} times in the past hour, potential anomaly`);
// Report to security analyzer
if (window.securityAnalyzer) {
window.securityAnalyzer.reportThreat('excessive_permission_usage', {
permission,
count,
timeWindow: '1 hour'
});
}
}
generateRequestId() {
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// Get permission report
getPermissionReport() {
const totalPermissions = this.grantedPermissions.size;
const requiredCount = Array.from(this.grantedPermissions).filter(p =>
this.requiredPermissions.has(p)
).length;
const optionalCount = totalPermissions - requiredCount;
const usageStats = {};
this.permissionUsage.forEach((logs, permission) => {
usageStats[permission] = {
totalUsage: logs.length,
recentUsage: logs.filter(log =>
Date.now() - log.timestamp < 24 * 60 * 60 * 1000
).length,
lastUsed: logs.length > 0 ? Math.max(...logs.map(log => log.timestamp)) : null
};
});
return {
summary: {
totalPermissions,
requiredPermissions: requiredCount,
optionalPermissions: optionalCount,
pendingRequests: Array.from(this.permissionRequests.values()).filter(req =>
req.status === 'pending'
).length
},
permissions: Array.from(this.grantedPermissions),
usageStats,
recentRequests: Array.from(this.permissionRequests.values())
.filter(req => Date.now() - req.timestamp < 24 * 60 * 60 * 1000)
.sort((a, b) => b.timestamp - a.timestamp)
};
}
}
// Global permission manager instance
const permissionManager = new PermissionManager();
// Export for use by other modules
window.permissionManager = permissionManager;
12.3 Data Encryption and Protection
12.3.1 Data Encryption Implementation
// crypto-manager.js - Encryption manager
class CryptoManager {
constructor() {
this.keyCache = new Map();
this.encryptionAlgorithm = 'AES-GCM';
this.keyDerivationAlgorithm = 'PBKDF2';
this.hashAlgorithm = 'SHA-256';
this.init();
}
async init() {
// Check browser encryption support
if (!window.crypto || !window.crypto.subtle) {
throw new Error('Browser does not support Web Crypto API');
}
console.log('Encryption manager initialized');
}
// Generate random key
async generateKey(algorithm = this.encryptionAlgorithm) {
try {
const key = await crypto.subtle.generateKey(
{
name: algorithm,
length: 256
},
true, // Extractable
['encrypt', 'decrypt']
);
return key;
} catch (error) {
console.error('Key generation failed:', error);
throw error;
}
}
// Derive key from password
async deriveKeyFromPassword(password, salt = null) {
// If no salt provided, generate a new one
if (!salt) {
salt = crypto.getRandomValues(new Uint8Array(16));
}
const encoder = new TextEncoder();
const passwordBuffer = encoder.encode(password);
// Import password as key material
const keyMaterial = await crypto.subtle.importKey(
'raw',
passwordBuffer,
{ name: this.keyDerivationAlgorithm },
false,
['deriveKey']
);
// Derive AES key
const derivedKey = await crypto.subtle.deriveKey(
{
name: this.keyDerivationAlgorithm,
salt: salt,
iterations: 100000, // OWASP recommended minimum
hash: this.hashAlgorithm
},
keyMaterial,
{
name: this.encryptionAlgorithm,
length: 256
},
false, // Not extractable (more secure)
['encrypt', 'decrypt']
);
return {
key: derivedKey,
salt: salt
};
}
// Encrypt data
async encryptData(data, key = null) {
try {
// If no key provided, generate a new one
if (!key) {
key = await this.generateKey();
}
// Generate random IV
const iv = crypto.getRandomValues(new Uint8Array(12));
// Convert data to ArrayBuffer
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(JSON.stringify(data));
// Perform encryption
const encrypted = await crypto.subtle.encrypt(
{
name: this.encryptionAlgorithm,
iv: iv
},
key,
dataBuffer
);
// Export key (if extractable)
let exportedKey = null;
try {
exportedKey = await crypto.subtle.exportKey('jwk', key);
} catch (e) {
// Key is not extractable, this is normal
}
return {
encrypted: Array.from(new Uint8Array(encrypted)),
iv: Array.from(iv),
key: exportedKey,
algorithm: this.encryptionAlgorithm
};
} catch (error) {
console.error('Data encryption failed:', error);
throw error;
}
}
// Decrypt data
async decryptData(encryptedData, key = null) {
try {
let cryptoKey = key;
// If provided key is exported, need to import it
if (key && typeof key === 'object' && key.kty) {
cryptoKey = await crypto.subtle.importKey(
'jwk',
key,
{ name: this.encryptionAlgorithm },
false,
['decrypt']
);
}
if (!cryptoKey) {
throw new Error('Decryption key is not available');
}
// Convert data format
const encrypted = new Uint8Array(encryptedData.encrypted);
const iv = new Uint8Array(encryptedData.iv);
// Perform decryption
const decrypted = await crypto.subtle.decrypt(
{
name: encryptedData.algorithm || this.encryptionAlgorithm,
iv: iv
},
cryptoKey,
encrypted
);
// Convert back to original data
const decoder = new TextDecoder();
const decryptedString = decoder.decode(decrypted);
return JSON.parse(decryptedString);
} catch (error) {
console.error('Data decryption failed:', error);
throw error;
}
}
// Calculate data hash
async hashData(data, algorithm = this.hashAlgorithm) {
try {
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(JSON.stringify(data));
const hashBuffer = await crypto.subtle.digest(algorithm, dataBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
} catch (error) {
console.error('Data hashing failed:', error);
throw error;
}
}
// Generate HMAC
async generateHMAC(data, secret) {
try {
const encoder = new TextEncoder();
// Import key
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(secret),
{ name: 'HMAC', hash: this.hashAlgorithm },
false,
['sign']
);
// Generate HMAC
const signature = await crypto.subtle.sign(
'HMAC',
key,
encoder.encode(JSON.stringify(data))
);
const signatureArray = Array.from(new Uint8Array(signature));
return signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
} catch (error) {
console.error('HMAC generation failed:', error);
throw error;
}
}
// Verify HMAC
async verifyHMAC(data, signature, secret) {
try {
const expectedSignature = await this.generateHMAC(data, secret);
return expectedSignature === signature;
} catch (error) {
console.error('HMAC verification failed:', error);
return false;
}
}
// Secure data storage
async secureStore(key, data, password = null) {
try {
let encryptionKey;
if (password) {
// Derive key from password
const derivedKeyInfo = await this.deriveKeyFromPassword(password);
encryptionKey = derivedKeyInfo.key;
// Store salt for later use
const saltKey = `${key}_salt`;
await chrome.storage.local.set({
[saltKey]: Array.from(derivedKeyInfo.salt)
});
} else {
// Generate random key and store it
encryptionKey = await this.generateKey();
const exportedKey = await crypto.subtle.exportKey('jwk', encryptionKey);
const keyStorageKey = `${key}_key`;
await chrome.storage.local.set({
[keyStorageKey]: exportedKey
});
}
// Encrypt data
const encryptedData = await this.encryptData(data, encryptionKey);
// Store encrypted data
await chrome.storage.local.set({
[key]: {
encrypted: encryptedData.encrypted,
iv: encryptedData.iv,
algorithm: encryptedData.algorithm,
timestamp: Date.now(),
protected: !!password
}
});
console.log(`Data securely stored: ${key}`);
return true;
} catch (error) {
console.error('Secure storage failed:', error);
throw error;
}
}
// Securely retrieve data
async secureRetrieve(key, password = null) {
try {
// Get encrypted data
const result = await chrome.storage.local.get(key);
const encryptedData = result[key];
if (!encryptedData) {
return null;
}
let decryptionKey;
if (encryptedData.protected && password) {
// Get salt and re-derive key
const saltResult = await chrome.storage.local.get(`${key}_salt`);
const salt = new Uint8Array(saltResult[`${key}_salt`]);
const derivedKeyInfo = await this.deriveKeyFromPassword(password, salt);
decryptionKey = derivedKeyInfo.key;
} else if (!encryptedData.protected) {
// Get stored key
const keyResult = await chrome.storage.local.get(`${key}_key`);
const exportedKey = keyResult[`${key}_key`];
if (exportedKey) {
decryptionKey = await crypto.subtle.importKey(
'jwk',
exportedKey,
{ name: this.encryptionAlgorithm },
false,
['decrypt']
);
}
} else {
throw new Error('Password required to decrypt protected data');
}
if (!decryptionKey) {
throw new Error('Decryption key is not available');
}
// Decrypt data
const decryptedData = await this.decryptData(encryptedData, decryptionKey);
console.log(`Data securely retrieved: ${key}`);
return decryptedData;
} catch (error) {
console.error('Secure retrieval failed:', error);
throw error;
}
}
// Securely delete data
async secureDelete(key) {
try {
const keysToRemove = [
key,
`${key}_key`,
`${key}_salt`
];
await chrome.storage.local.remove(keysToRemove);
// Clear key cache
this.keyCache.delete(key);
console.log(`Secure data deleted: ${key}`);
return true;
} catch (error) {
console.error('Secure deletion failed:', error);
throw error;
}
}
// Generate secure random numbers
generateSecureRandom(length = 32) {
return Array.from(crypto.getRandomValues(new Uint8Array(length)));
}
// Generate secure random string
generateSecureRandomString(length = 32) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const randomBytes = crypto.getRandomValues(new Uint8Array(length));
return Array.from(randomBytes)
.map(byte => chars[byte % chars.length])
.join('');
}
// Password strength check
checkPasswordStrength(password) {
const checks = {
length: password.length >= 12,
uppercase: /[A-Z]/.test(password),
lowercase: /[a-z]/.test(password),
numbers: /[0-9]/.test(password),
symbols: /[^A-Za-z0-9]/.test(password),
noCommonWords: !this.containsCommonWords(password),
noRepeatedChars: !this.hasRepeatedChars(password)
};
const score = Object.values(checks).filter(Boolean).length;
const maxScore = Object.keys(checks).length;
let strength;
if (score < 3) strength = 'weak';
else if (score < 5) strength = 'medium';
else if (score < 7) strength = 'strong';
else strength = 'very_strong';
return {
strength,
score,
maxScore,
checks,
suggestions: this.getPasswordSuggestions(checks)
};
}
containsCommonWords(password) {
const commonWords = [
'password', '123456', 'qwerty', 'admin', 'welcome',
'letmein', 'monkey', 'dragon', 'master', 'login'
];
return commonWords.some(word =>
password.toLowerCase().includes(word)
);
}
hasRepeatedChars(password) {
return /(.)\1{2,}/.test(password); // Check for 3 or more consecutive identical characters
}
getPasswordSuggestions(checks) {
const suggestions = [];
if (!checks.length) suggestions.push('Use at least 12 characters');
if (!checks.uppercase) suggestions.push('Include uppercase letters');
if (!checks.lowercase) suggestions.push('Include lowercase letters');
if (!checks.numbers) suggestions.push('Include numbers');
if (!checks.symbols) suggestions.push('Include special symbols');
if (!checks.noCommonWords) suggestions.push('Avoid common words');
if (!checks.noRepeatedChars) suggestions.push('Avoid repeated characters');
return suggestions;
}
// Data integrity verification
async createIntegrityChecksum(data) {
const hash = await this.hashData(data);
const timestamp = Date.now();
return {
checksum: hash,
timestamp,
algorithm: this.hashAlgorithm
};
}
async verifyIntegrity(data, integrity) {
try {
const currentHash = await this.hashData(data);
return currentHash === integrity.checksum;
} catch (error) {
console.error('Integrity verification failed:', error);
return false;
}
}
// Get encryption status report
getCryptoReport() {
const supportedAlgorithms = {
encryption: [this.encryptionAlgorithm],
hashing: [this.hashAlgorithm],
keyDerivation: [this.keyDerivationAlgorithm]
};
const securityFeatures = {
webCryptoSupported: !!window.crypto.subtle,
randomGenerationSupported: !!window.crypto.getRandomValues,
keyStorage: 'chrome.storage.local',
passwordProtection: true,
integrityChecking: true
};
return {
supportedAlgorithms,
securityFeatures,
keyCache: {
size: this.keyCache.size,
keys: Array.from(this.keyCache.keys())
}
};
}
}
// Global encryption manager instance
const cryptoManager = new CryptoManager();
// Export for use by other modules
window.cryptoManager = cryptoManager;
warning Security Precautions
- Minimize Permissions: Only request necessary permissions, avoid excessive permissions
- Data Encryption: Sensitive data must be encrypted for storage
- Input Validation: Strictly validate all user inputs
- HTTPS Enforcement: All network communications must use HTTPS
- Regular Audits: Regularly check permission usage and security configuration
tip Best Practices
- Implement multiple layers of defense
- Use strong passwords and multi-factor authentication
- Regularly update security policies and dependencies
- Establish security incident response processes
- Conduct security testing and code reviews
note Summary This chapter provided an in-depth introduction to Chrome extension security architecture, permission management, and data protection techniques. Security is one of the most important considerations in extension development, directly impacting user trust and extension success. In the next chapter, we will learn testing and debugging techniques to ensure extension quality and stability.