This extension implements comprehensive Content Security Policy (CSP) headers to prevent XSS attacks and other security vulnerabilities.
"content_security_policy": {
"extension_pages": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self'; connect-src 'self'; img-src 'self' data:; font-src 'self';"
}<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'; base-uri 'self'; connect-src 'self'; img-src 'self' data:; font-src 'self';">- default-src 'self': Only allow resources from the same origin
- script-src 'self': Only allow scripts from the same origin
- style-src 'self': Only allow styles from the same origin
- object-src 'none': Block all plugins (Flash, Java, etc.)
- base-uri 'self': Restrict base URI to same origin
- connect-src 'self': Only allow connections to same origin
- img-src 'self' data: Allow images from same origin and data URIs
- font-src 'self': Only allow fonts from same origin
- XSS Prevention: Blocks inline scripts and external script sources
- Clickjacking Protection: Prevents malicious framing
- Code Injection Prevention: Restricts resource loading to trusted sources
- Data Exfiltration Prevention: Limits network connections
- The manifest CSP allows
'unsafe-inline'for scripts and styles to maintain functionality - HTML files use stricter CSP without
'unsafe-inline'for better security - All external resources are blocked except for data URIs for images
- Base URI is restricted to prevent base tag hijacking
To test if CSP is working correctly:
- Open browser developer tools
- Check the Console tab for CSP violation messages
- Verify that no external scripts or styles are loaded
- Confirm that inline event handlers are blocked
- Regular Updates: Keep CSP policies updated as the extension evolves
- Monitoring: Monitor CSP violation reports in production
- Testing: Test CSP policies thoroughly before deployment
- Documentation: Keep this security documentation updated
If CSP violations occur, they will appear in the browser console. Common violations to watch for:
- Inline script execution attempts
- External resource loading attempts
- Unsafe eval() usage
- Inline event handler usage
If CSP violations are detected:
- Identify the source of the violation
- Update CSP policy to allow necessary resources (if safe)
- Refactor code to avoid unsafe practices
- Test thoroughly before deploying changes
The extension implements secure error handling to prevent exposure of sensitive information while maintaining functionality.
- No Stack Trace Exposure: Never expose
error.stackto users or logs - Sanitized Error Messages: Use user-friendly error messages instead of raw error details
- Contextual Logging: Log only necessary information for debugging
- Error Classification: Map errors to user-friendly messages
- Safe Error Responses: Return sanitized error information
// Safe error logging
function logErrorSafely(error, context = '') {
const errorInfo = {
name: error.name || 'UnknownError',
context: context,
timestamp: new Date().toISOString()
};
console.error('Extension Error:', {
name: errorInfo.name,
context: errorInfo.context,
timestamp: errorInfo.timestamp
});
}
// User-friendly error messages
function getUserFriendlyError(error) {
const errorMap = {
'NetworkError': 'Network connection failed. Please check your internet connection.',
'TimeoutError': 'Request timed out. Please try again.',
'PermissionError': 'Permission denied. Please check extension permissions.',
'UNKNOWN_ERROR': 'An unexpected error occurred. Please try again.'
};
return errorMap[error.name] || errorMap['UNKNOWN_ERROR'];
}- No Sensitive Data Exposure: Error messages don't reveal internal structure
- User-Friendly Messages: Clear, actionable error messages for users
- Debugging Support: Sufficient logging for developers without exposing details
- Consistent Error Handling: Standardized approach across the extension
- Never expose stack traces to users or in production logs
- Use error codes instead of detailed error messages
- Sanitize all error responses before sending to users
- Log only necessary information for debugging
- Provide actionable error messages to users
- Handle errors gracefully without crashing the extension
To test secure error handling:
- Trigger various error conditions
- Verify no sensitive information is exposed
- Check that user-friendly messages are displayed
- Confirm error logging is appropriate for debugging
- Test error recovery mechanisms
The extension implements secure debug logging that prevents exposure of sensitive information in production environments.
const LOG_CONFIG = {
debugMode: false, // Disabled in production
logLevel: 'error', // Only log errors in production
isDevelopment: process.env.NODE_ENV === 'development',
isProduction: process.env.NODE_ENV === 'production'
};function secureLog(message, level = 'info', context = '') {
// Only log if debug mode is enabled
if (!LOG_CONFIG.debugMode) {
return;
}
// In production, only log errors and warnings
if (LOG_CONFIG.isProduction && level === 'debug') {
return;
}
// Sanitize the message
const sanitizedMessage = sanitizeLogMessage(message);
console.log(`[GTM Inspector] [${timestamp}] ${context}: ${sanitizedMessage}`);
}- URLs and extension URLs
- Email addresses
- Credit card numbers
- IBAN numbers
- Passport numbers
- Internal extension paths
function sanitizeLogMessage(message) {
const sensitivePatterns = [
/https?:\/\/[^\s]+/g, // URLs
/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, // Email addresses
/\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/g, // Credit card numbers
// ... more patterns
];
let sanitized = message;
sensitivePatterns.forEach(pattern => {
sanitized = sanitized.replace(pattern, '[REDACTED]');
});
return sanitized;
}- No Sensitive Data Exposure: All sensitive data is redacted from logs
- Production-Safe: Debug logging is disabled in production
- Level-Based Logging: Different log levels for different environments
- Sanitized Output: All log messages are sanitized before output
- Environment Awareness: Different behavior in development vs production
- Disable in Production: Set
debugMode: falsefor production builds - Sanitize All Messages: Always sanitize log messages before output
- Use Appropriate Levels: Use error/warn for production, debug for development
- Avoid Sensitive Data: Never log passwords, tokens, or personal data
- Environment Detection: Use environment variables to control logging
- Regular Audits: Regularly review logging for sensitive data exposure
To test secure debug logging:
- Set
debugMode: trueand test with sensitive data - Verify sensitive data is redacted in logs
- Set
debugMode: falseand verify no logging occurs - Test different log levels in different environments
- Verify no sensitive URLs or data appear in console
The extension implements comprehensive input validation to prevent malicious data injection and ensure data integrity.
const InputValidator = {
// Validate message origin
isValidOrigin(origin) {
if (!origin) return false;
// Allow same origin
if (origin === window.location.origin) return true;
// Allow extension origin
if (origin.startsWith('chrome-extension://')) return true;
// Allow specific trusted domains
const trustedDomains = [
'https://vermillion-zuccutto-ed1811.netlify.app',
'https://cookiebot.com',
'https://consent.cookiebot.com'
];
return trustedDomains.some(domain => origin.startsWith(domain));
},
// Validate message structure
isValidMessage(message) {
if (!message || typeof message !== 'object') return false;
// Check for required fields
if (!message.source || !message.action) return false;
// Validate source
const validSources = ['gtm-inspector-content', 'gtm-inspector-page'];
if (!validSources.includes(message.source)) return false;
// Validate action
const validActions = [
'detectGTM', 'getTagStatus', 'getEvents', 'updateConsent',
'updateSimulationMode', 'clearEventLog', 'runDiagnostics',
'getTagManagerInteractions', 'ping'
];
if (!validActions.includes(message.action)) return false;
return true;
}
};// Validate consent data
isValidConsentData(consent) {
if (!consent || typeof consent !== 'object') return false;
const requiredFields = [
'analytics_storage', 'ad_storage', 'functionality_storage',
'personalization_storage', 'security_storage'
];
const validValues = ['granted', 'denied'];
for (const field of requiredFields) {
if (!(field in consent)) return false;
if (!validValues.includes(consent[field])) return false;
}
return true;
}// Sanitize string input
sanitizeString(input, maxLength = 1000) {
if (typeof input !== 'string') return '';
// Remove potentially dangerous characters
let sanitized = input
.replace(/[<>]/g, '') // Remove angle brackets
.replace(/javascript:/gi, '') // Remove javascript: protocol
.replace(/data:/gi, '') // Remove data: protocol
.trim();
// Limit length
if (sanitized.length > maxLength) {
sanitized = sanitized.substring(0, maxLength);
}
return sanitized;
}- Origin Validation: Only accepts messages from trusted sources
- Structure Validation: Ensures proper message format
- Data Type Validation: Validates data types and values
- Input Sanitization: Removes potentially dangerous content
- Length Limits: Prevents buffer overflow attacks
- Protocol Filtering: Blocks dangerous protocols
- Origin validation for all incoming messages
- Message structure validation
- Action whitelist validation
- Data sanitization before processing
- Request structure validation
- Action validation
- Parameter type checking
- Data sanitization
- Required field validation
- Value validation (granted/denied only)
- Structure validation
- Type checking
- Timestamp validation
- Event type validation
- Data structure validation
- Size limits
- Validate at Entry Points: Validate all external data at the boundary
- Whitelist Validation: Use whitelists instead of blacklists
- Type Checking: Validate data types before processing
- Length Limits: Set reasonable limits on input size
- Sanitization: Clean input before processing
- Error Handling: Provide clear error messages for invalid input
- Logging: Log validation failures for monitoring
To test input validation:
- Send malformed messages to postMessage handlers
- Test with invalid consent data structures
- Send oversized data to test length limits
- Test with malicious protocols in strings
- Verify error responses are appropriate
- Test origin validation with untrusted domains
The extension implements AES-256-GCM encryption for sensitive stored data to prevent unauthorized access and data breaches.
{
simulationMode: true/false,
simulatedConsent: {
analytics_storage: 'granted'/'denied',
ad_storage: 'granted'/'denied',
functionality_storage: 'granted'/'denied',
personalization_storage: 'granted'/'denied',
security_storage: 'granted'/'denied'
}
}{
gtmInspectorEvents: [
{
id: 'uuid',
type: 'consent_change'/'tag_fired'/'tag_blocked',
timestamp: Date.now(),
url: 'https://example.com/page', // SENSITIVE
website: 'Website Name', // SENSITIVE
consent: { /* consent data */ }, // SENSITIVE
data: { /* event details */ } // SENSITIVE
}
]
}{
tagManagerInteractions: [
{
type: 'tag_fired'/'tag_blocked',
timestamp: Date.now(),
url: 'https://example.com', // SENSITIVE
website: 'Website Name', // SENSITIVE
tagData: { /* tag information */ } // SENSITIVE
}
]
}{
lastCookiebotConsentChange: {
action: 'accept'/'decline'/'ready',
website: 'Website Name', // SENSITIVE
url: 'https://example.com/page', // SENSITIVE
consent: { /* consent data */ }, // SENSITIVE
timestamp: Date.now()
}
}const EncryptionManager = {
// Generate a secure encryption key (derived from extension ID)
async getEncryptionKey() {
const manifest = chrome.runtime.getManifest();
const extensionId = chrome.runtime.id;
// Create a deterministic key from extension ID
const encoder = new TextEncoder();
const data = encoder.encode(extensionId + manifest.version);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const key = hashArray.slice(0, 32); // Use first 32 bytes for AES-256
return crypto.subtle.importKey(
'raw',
new Uint8Array(key),
{ name: 'AES-GCM' },
false,
['encrypt', 'decrypt']
);
},
// Encrypt sensitive data
async encryptData(data) {
const key = await this.getEncryptionKey();
const iv = crypto.getRandomValues(new Uint8Array(12));
const encoder = new TextEncoder();
const encodedData = encoder.encode(JSON.stringify(data));
const encryptedBuffer = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
encodedData
);
const encryptedArray = new Uint8Array(encryptedBuffer);
const combined = new Uint8Array(iv.length + encryptedArray.length);
combined.set(iv);
combined.set(encryptedArray, iv.length);
return btoa(String.fromCharCode(...combined));
}
};// Encrypt data before storage
async saveState() {
const encryptedConsent = await EncryptionManager.encryptAndMark(this.simulatedConsent);
await chrome.storage.local.set({
simulationMode: this.simulationMode,
simulatedConsent: encryptedConsent
});
}
// Decrypt data after retrieval
async loadState() {
const result = await chrome.storage.local.get(['simulatedConsent']);
if (EncryptionManager.isEncrypted(result.simulatedConsent)) {
this.simulatedConsent = await EncryptionManager.decryptMarked(result.simulatedConsent);
}
}- AES-256-GCM Encryption: Military-grade encryption algorithm
- Deterministic Key Generation: Keys derived from extension ID and version
- Random IV: Each encryption uses a unique initialization vector
- Data Integrity: GCM mode provides authentication and integrity
- Automatic Encryption: All sensitive data automatically encrypted
- Backward Compatibility: Handles both encrypted and unencrypted data
- Key Management: Keys derived from extension identity
- IV Generation: Random IV for each encryption operation
- Error Handling: Graceful fallback if encryption fails
- Data Marking: Clear identification of encrypted data
- Performance: Efficient encryption/decryption operations
- Compatibility: Works with existing storage mechanisms
To test data encryption:
- Save sensitive data and verify it's encrypted in storage
- Load encrypted data and verify it decrypts correctly
- Test with corrupted encrypted data
- Verify encryption key consistency across sessions
- Test performance impact of encryption/decryption
- Verify backward compatibility with unencrypted data
The extension implements secure postMessage communication by using specific origins instead of wildcard ('*') targets to prevent cross-site scripting attacks.
// UNSAFE - Allows any origin to receive the message
window.postMessage(data, '*');// SAFE - Only allows same origin to receive the message
window.postMessage(data, window.location.origin);// ❌ BEFORE (UNSAFE)
window.postMessage({
source: 'gtm-inspector-content',
action: action,
data: data,
id: messageId
}, '*');
// ✅ AFTER (SECURE)
window.postMessage({
source: 'gtm-inspector-content',
action: action,
data: data,
id: messageId
}, window.location.origin);// ❌ BEFORE (UNSAFE)
window.postMessage(response, '*');
// ✅ AFTER (SECURE)
window.postMessage(response, event.origin || window.location.origin);// ❌ BEFORE (UNSAFE)
window.postMessage({
type: 'COOKIEBOT_CONSENT_CHANGE',
data: notificationData
}, '*');
// ✅ AFTER (SECURE)
window.postMessage({
type: 'COOKIEBOT_CONSENT_CHANGE',
data: notificationData
}, window.location.origin);- Origin Restriction: Messages only sent to same origin
- XSS Prevention: Prevents malicious sites from intercepting messages
- Data Protection: Sensitive data not exposed to other origins
- Attack Mitigation: Reduces attack surface for postMessage exploits
- Privacy Protection: Ensures communication stays within trusted context
- Use Specific Origins: Never use
'*'as target origin - Validate Origins: Always validate incoming message origins
- Sanitize Data: Clean data before sending via postMessage
- Error Handling: Handle cases where origin is unavailable
- Fallback Strategy: Provide fallback for edge cases
- Testing: Test with different origin scenarios
// Validate incoming message origins
function isValidOrigin(origin) {
if (!origin) return false;
// Allow same origin
if (origin === window.location.origin) return true;
// Allow extension origin
if (origin.startsWith('chrome-extension://')) return true;
// Allow specific trusted domains
const trustedDomains = [
'https://vermillion-zuccutto-ed1811.netlify.app',
'https://cookiebot.com',
'https://consent.cookiebot.com'
];
return trustedDomains.some(domain => origin.startsWith(domain));
}To test postMessage security:
- Send messages from different origins
- Verify only same-origin messages are processed
- Test with malicious origin attempts
- Verify fallback behavior when origin is unavailable
- Test cross-origin communication scenarios
- Verify data integrity across origins
The extension implements comprehensive rate limiting to prevent abuse, protect resources, and mitigate various attack vectors including storage overflow, performance degradation, and data exfiltration attempts.
// Attacker can flood storage with fake events
for (let i = 0; i < 10000; i++) {
chrome.storage.local.set({ gtmInspectorEvents: fakeEvents });
}
// Result: Storage quota exceeded, extension breaks// Attacker can spam messages to freeze extension
for (let i = 0; i < 1000; i++) {
chrome.runtime.sendMessage({action: 'getEvents'});
}
// Result: UI freezing, memory exhaustion// Attacker can rapidly query sensitive data
for (let i = 0; i < 500; i++) {
chrome.runtime.sendMessage({action: 'getConsentData'});
}
// Result: Potential data leakage, privacy violationclass RateLimiter {
constructor() {
this.operations = new Map();
this.defaultLimits = {
storage: { max: 10, window: 60000 }, // 10 operations per minute
messages: { max: 20, window: 60000 }, // 20 messages per minute
events: { max: 50, window: 60000 }, // 50 events per minute
consent: { max: 5, window: 60000 } // 5 consent changes per minute
};
}
isAllowed(operation, key = 'default') {
const limit = this.defaultLimits[operation] || this.defaultLimits.messages;
const now = Date.now();
const keyName = `${operation}_${key}`;
if (!this.operations.has(keyName)) {
this.operations.set(keyName, []);
}
const operations = this.operations.get(keyName);
// Remove old operations outside the window
const validOperations = operations.filter(time => now - time < limit.window);
this.operations.set(keyName, validOperations);
// Check if we're under the limit
if (validOperations.length < limit.max) {
validOperations.push(now);
this.operations.set(keyName, validOperations);
return true;
}
return false;
}
}// ❌ BEFORE (VULNERABLE)
await chrome.storage.local.set({ gtmInspectorEvents: events });
// ✅ AFTER (SECURE)
async function rateLimitedStorageSet(data) {
if (!rateLimiter.isAllowed('storage')) {
const remainingTime = rateLimiter.getRemainingTime('storage');
throw new Error(`Rate limit exceeded. Try again in ${Math.ceil(remainingTime / 1000)} seconds.`);
}
return await chrome.storage.local.set(data);
}
await rateLimitedStorageSet({ gtmInspectorEvents: events });// ❌ BEFORE (VULNERABLE)
chrome.runtime.sendMessage({action: 'cookiebotConsentChange', data: {...}});
// ✅ AFTER (SECURE)
async function rateLimitedSendMessage(message) {
if (!rateLimiter.isAllowed('messages')) {
const remainingTime = rateLimiter.getRemainingTime('messages');
throw new Error(`Message rate limit exceeded. Try again in ${Math.ceil(remainingTime / 1000)} seconds.`);
}
return await chrome.runtime.sendMessage(message);
}
await rateLimitedSendMessage({action: 'cookiebotConsentChange', data: {...}});- Limit: 10 operations per minute
- Purpose: Prevent storage quota exhaustion
- Protection: Against storage overflow attacks
- Limit: 20 messages per minute
- Purpose: Prevent performance degradation
- Protection: Against message flooding attacks
- Limit: 50 events per minute
- Purpose: Prevent excessive event logging
- Protection: Against log flooding attacks
- Limit: 5 changes per minute
- Purpose: Prevent consent manipulation
- Protection: Against consent abuse attacks
- Storage Protection: Prevents quota exhaustion attacks
- Performance Protection: Prevents UI freezing and memory exhaustion
- Data Protection: Prevents rapid data exfiltration attempts
- Resource Protection: Ensures extension remains responsive
- Attack Mitigation: Reduces attack surface for various exploits
- User Experience: Maintains smooth operation under attack
- Per-Operation Limits: Different limits for different operations
- Time Windows: Sliding window approach for accurate limiting
- User Feedback: Clear error messages with retry times
- Graceful Degradation: Extension continues working under limits
- Monitoring: Log rate limit violations for security analysis
- Configuration: Adjustable limits based on usage patterns
try {
await rateLimitedStorageSet(data);
} catch (error) {
if (error.message.includes('Rate limit exceeded')) {
console.warn('Rate limit exceeded for storage operation');
showNotification('Too many operations. Please wait a moment.', 'warning');
} else {
console.error('Storage error:', error);
throw error;
}
}To test rate limiting security:
- Storage Attack Test: Rapidly call storage operations
- Message Flood Test: Send many messages quickly
- Event Spam Test: Generate excessive events
- Consent Abuse Test: Rapidly change consent states
- Recovery Test: Verify normal operation after limits
- Edge Case Test: Test boundary conditions and timeouts
The extension implements cryptographically secure random ID generation to replace predictable timestamp-based IDs, preventing enumeration attacks and ensuring unique, non-guessable identifiers.
// Attacker can predict and enumerate IDs
const id = Date.now() + Math.random();
// Result: Predictable, enumerable, vulnerable to timing attacks// Multiple events can have same timestamp
const id = Date.now();
// Result: ID collisions, data corruption, security bypass// Attacker can guess future IDs
const id = Date.now() + Math.random();
// Result: Predictable sequence, data enumeration possible// ❌ BEFORE (INSECURE)
generateUUID: function() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
// ✅ AFTER (SECURE)
generateUUID: function() {
// Use cryptographically secure random values
const array = new Uint8Array(16);
crypto.getRandomValues(array);
// Set version (4) and variant bits
array[6] = (array[6] & 0x0f) | 0x40; // Version 4
array[8] = (array[8] & 0x3f) | 0x80; // Variant 1
// Convert to hex string
const hex = Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
// Format as UUID
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
}// ✅ SECURE - Cryptographically random ID
generateSecureId: function() {
const array = new Uint8Array(16);
crypto.getRandomValues(array);
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
}// ❌ BEFORE (VULNERABLE)
const messageId = Date.now() + Math.random();
// ✅ AFTER (SECURE)
const messageId = realEventLogger.generateSecureId();// ❌ BEFORE (VULNERABLE)
const interaction = {
id: Date.now() + Math.random(),
type: type,
data: data,
url: window.location.href,
timestamp: Date.now()
};
// ✅ AFTER (SECURE)
const interaction = {
id: realEventLogger.generateSecureId(),
type: type,
data: data,
url: window.location.href,
timestamp: Date.now()
};- Unpredictability: IDs cannot be guessed or predicted
- Uniqueness: Extremely low collision probability
- Enumeration Resistance: Prevents ID enumeration attacks
- Timing Attack Resistance: No correlation with time
- Data Integrity: Prevents ID-based data manipulation
- Privacy Protection: IDs reveal no information about timing or sequence
- Source:
crypto.getRandomValues()- cryptographically secure - Entropy: 128 bits of entropy per ID
- Distribution: Uniform random distribution
- Collision Resistance: 2^64 collision resistance
- Version: UUID v4 (random)
- Variant: RFC 4122 variant 1
- Format: Standard UUID format (8-4-4-4-12)
- Compatibility: Compatible with UUID libraries
- ✅
content.js- Event logging and message IDs - ✅
injected-script.js- Page context event IDs - ✅
popup/popup.js- UI event tracking
- ✅
universal-cookiebot-integration.js- Consent change IDs - ✅
website-integration.js- Website integration IDs - ✅
universal-bookmarklet.js- Bookmarklet event IDs
- Use Crypto API: Always use
crypto.getRandomValues() - Avoid Math.random(): Never use for security-critical IDs
- Sufficient Entropy: Use at least 128 bits of entropy
- Consistent Format: Use standardized UUID format
- Error Handling: Handle crypto API failures gracefully
- Validation: Validate ID format and uniqueness
function generateSecureId() {
try {
const array = new Uint8Array(16);
crypto.getRandomValues(array);
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
} catch (error) {
console.error('Crypto API unavailable, using fallback');
// Fallback to timestamp + random (less secure but functional)
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
}To test secure ID generation:
- Uniqueness Test: Generate many IDs and check for collisions
- Randomness Test: Verify uniform distribution
- Format Test: Validate UUID format compliance
- Performance Test: Measure generation speed
- Fallback Test: Test behavior when crypto API unavailable
- Security Test: Attempt to predict or enumerate IDs