## Major Enhancements ### 🏗️ Architecture & Infrastructure - Implement comprehensive Docker testing infrastructure with hermetic environment - Add Forgejo Actions CI/CD pipeline for automated deployments - Create Page Object Model (POM) testing architecture reducing test duplication by 90% - Establish security-first development patterns with input validation and output escaping ### 🧪 Testing Framework Modernization - Migrate 146+ tests from 80 duplicate files to centralized architecture - Add comprehensive E2E test suites for all user roles and workflows - Implement WordPress error detection with automatic site health monitoring - Create robust browser lifecycle management with proper cleanup ### 📚 Documentation & Guides - Add comprehensive development best practices guide - Create detailed administrator setup documentation - Establish user guides for trainers and master trainers - Document security incident reports and migration guides ### 🔧 Core Plugin Features - Enhance trainer profile management with certification system - Improve find trainer functionality with advanced filtering - Strengthen master trainer area with content management - Add comprehensive venue and organizer management ### 🛡️ Security & Reliability - Implement security-first patterns throughout codebase - Add comprehensive input validation and output escaping - Create secure credential management system - Establish proper WordPress role-based access control ### 🎯 WordPress Integration - Strengthen singleton pattern implementation across all classes - Enhance template hierarchy with proper WordPress integration - Improve page manager with hierarchical URL structure - Add comprehensive shortcode and menu system ### 🔍 Developer Experience - Add extensive debugging and troubleshooting tools - Create comprehensive test data seeding scripts - Implement proper error handling and logging - Establish consistent code patterns and standards ### 📊 Performance & Optimization - Optimize database queries and caching strategies - Improve asset loading and script management - Enhance template rendering performance - Streamline user experience across all interfaces 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
767 lines
No EOL
26 KiB
JavaScript
767 lines
No EOL
26 KiB
JavaScript
/**
|
|
* HVAC Testing Framework - Secure Browser Management
|
|
*
|
|
* Provides secure browser configuration with hardened security settings,
|
|
* SSL/TLS validation, and secure authentication handling for Playwright.
|
|
*
|
|
* Security Features:
|
|
* - Hardened browser security configuration
|
|
* - SSL/TLS certificate validation
|
|
* - Secure authentication and session management
|
|
* - Network request filtering and monitoring
|
|
* - Screenshot and trace security controls
|
|
*
|
|
* @author Claude Code - Emergency Security Response
|
|
* @version 1.0.0
|
|
* @security CRITICAL - Provides secure browser automation
|
|
*/
|
|
|
|
const { chromium, firefox, webkit } = require('playwright');
|
|
const fs = require('fs').promises;
|
|
const path = require('path');
|
|
const { getCredentialManager } = require('./SecureCredentialManager');
|
|
|
|
class SecureBrowserManager {
|
|
constructor() {
|
|
this.credentialManager = getCredentialManager();
|
|
this.activeBrowsers = new Map();
|
|
this.activeContexts = new Map();
|
|
this.securityConfig = this.loadSecurityConfiguration();
|
|
}
|
|
|
|
/**
|
|
* Load security configuration from environment
|
|
* @returns {Object} Security configuration
|
|
*/
|
|
loadSecurityConfiguration() {
|
|
return {
|
|
// Browser security settings
|
|
headless: process.env.PLAYWRIGHT_HEADLESS !== 'false',
|
|
slowMo: parseInt(process.env.PLAYWRIGHT_SLOW_MO) || 0,
|
|
timeout: parseInt(process.env.PLAYWRIGHT_TIMEOUT) || 30000,
|
|
|
|
// SSL/TLS settings
|
|
tlsValidationMode: process.env.TLS_VALIDATION_MODE || 'strict',
|
|
ignoreCertificateErrors: process.env.TLS_VALIDATION_MODE === 'permissive',
|
|
|
|
// Security features
|
|
enableNetworkTracing: process.env.ENABLE_NETWORK_TRACE === 'true',
|
|
screenshotOnFailure: process.env.SCREENSHOT_ON_FAILURE !== 'false',
|
|
|
|
// Resource limits
|
|
maxMemoryMB: parseInt(process.env.MAX_BROWSER_MEMORY) || 512,
|
|
maxDiskMB: parseInt(process.env.MAX_BROWSER_DISK) || 100,
|
|
|
|
// Results directories
|
|
resultsDir: process.env.TEST_RESULTS_DIR || './test-results',
|
|
screenshotsDir: process.env.TEST_SCREENSHOTS_DIR || './test-screenshots'
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create secure browser instance with hardened configuration
|
|
* @param {string} browserType - Browser type (chromium, firefox, webkit)
|
|
* @param {Object} options - Additional browser options
|
|
* @returns {Promise<Object>} Browser instance with security metadata
|
|
*/
|
|
async createSecureBrowser(browserType = 'chromium', options = {}) {
|
|
const browsers = { chromium, firefox, webkit };
|
|
const browserEngine = browsers[browserType];
|
|
|
|
if (!browserEngine) {
|
|
throw new Error(`Unsupported browser type: ${browserType}`);
|
|
}
|
|
|
|
// Build secure launch options
|
|
const launchOptions = this.buildSecureLaunchOptions(browserType, options);
|
|
|
|
// Launch browser with security configuration
|
|
const browser = await browserEngine.launch(launchOptions);
|
|
const browserId = this.generateBrowserId();
|
|
|
|
// Store browser reference
|
|
this.activeBrowsers.set(browserId, {
|
|
browser,
|
|
browserType,
|
|
created: new Date().toISOString(),
|
|
options: launchOptions
|
|
});
|
|
|
|
// Log browser creation
|
|
await this.credentialManager.auditLogger('BROWSER_CREATED', {
|
|
browserId,
|
|
browserType,
|
|
headless: launchOptions.headless,
|
|
securityFeatures: this.getSecurityFeaturesSummary(launchOptions)
|
|
});
|
|
|
|
return {
|
|
browser,
|
|
browserId,
|
|
createSecureContext: (contextOptions = {}) =>
|
|
this.createSecureContext(browserId, contextOptions)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Build secure browser launch options
|
|
* @param {string} browserType - Browser type
|
|
* @param {Object} userOptions - User-provided options
|
|
* @returns {Object} Secure launch options
|
|
*/
|
|
buildSecureLaunchOptions(browserType, userOptions) {
|
|
const baseOptions = {
|
|
headless: this.securityConfig.headless,
|
|
slowMo: this.securityConfig.slowMo,
|
|
timeout: this.securityConfig.timeout
|
|
};
|
|
|
|
// Browser-specific security hardening
|
|
if (browserType === 'chromium') {
|
|
baseOptions.args = this.buildSecureChromiumArgs();
|
|
} else if (browserType === 'firefox') {
|
|
baseOptions.firefoxUserPrefs = this.buildSecureFirefoxPrefs();
|
|
}
|
|
|
|
// SSL/TLS configuration
|
|
baseOptions.ignoreHTTPSErrors = this.securityConfig.ignoreCertificateErrors;
|
|
|
|
// Merge with user options (security options take precedence)
|
|
const mergedOptions = { ...userOptions, ...baseOptions };
|
|
|
|
// Validate security-critical options
|
|
this.validateSecurityOptions(mergedOptions);
|
|
|
|
return mergedOptions;
|
|
}
|
|
|
|
/**
|
|
* Build secure Chromium arguments
|
|
* @returns {Array} Secure Chrome arguments
|
|
*/
|
|
buildSecureChromiumArgs() {
|
|
const secureArgs = [
|
|
// Security hardening
|
|
'--disable-web-security=false', // Enable web security
|
|
'--disable-features=TranslateUI',
|
|
'--disable-ipc-flooding-protection=false',
|
|
'--disable-renderer-backgrounding',
|
|
'--disable-backgrounding-occluded-windows',
|
|
'--disable-background-timer-throttling',
|
|
'--disable-component-extensions-with-background-pages',
|
|
|
|
// Memory and resource limits
|
|
'--max-old-space-size=512',
|
|
'--memory-pressure-off',
|
|
|
|
// Network security
|
|
'--no-proxy-server',
|
|
'--disable-sync',
|
|
'--disable-translate',
|
|
|
|
// Content security
|
|
'--disable-plugins',
|
|
'--disable-flash-3d',
|
|
'--disable-flash-stage3d'
|
|
];
|
|
|
|
// Add sandbox control based on environment
|
|
if (process.env.CONTAINER_MODE === 'true') {
|
|
// In containers, we need to disable sandbox due to user namespace issues
|
|
secureArgs.push('--no-sandbox', '--disable-setuid-sandbox');
|
|
console.warn('⚠️ Running in container mode with reduced sandbox security');
|
|
} else {
|
|
// In normal environments, keep sandbox enabled
|
|
console.log('✅ Running with full sandbox security');
|
|
}
|
|
|
|
return secureArgs;
|
|
}
|
|
|
|
/**
|
|
* Build secure Firefox preferences
|
|
* @returns {Object} Secure Firefox preferences
|
|
*/
|
|
buildSecureFirefoxPrefs() {
|
|
return {
|
|
// Security preferences
|
|
'security.tls.insecure_fallback_hosts': '',
|
|
'security.mixed_content.block_active_content': true,
|
|
'security.mixed_content.block_display_content': true,
|
|
|
|
// Privacy preferences
|
|
'privacy.trackingprotection.enabled': true,
|
|
'privacy.donottrackheader.enabled': true,
|
|
|
|
// Network preferences
|
|
'network.http.sendOriginHeader': 1,
|
|
'network.cookie.cookieBehavior': 1
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Validate security-critical browser options
|
|
* @param {Object} options - Browser options to validate
|
|
*/
|
|
validateSecurityOptions(options) {
|
|
// Check for dangerous options
|
|
const dangerousOptions = [
|
|
'devtools', // Don't allow devtools in automated testing
|
|
'userDataDir' // Prevent data persistence
|
|
];
|
|
|
|
for (const dangerous of dangerousOptions) {
|
|
if (options[dangerous]) {
|
|
throw new Error(`Security violation: ${dangerous} option not allowed`);
|
|
}
|
|
}
|
|
|
|
// Validate arguments for security
|
|
if (options.args) {
|
|
const dangerousArgs = [
|
|
'--disable-web-security',
|
|
'--allow-running-insecure-content',
|
|
'--disable-security-warnings'
|
|
];
|
|
|
|
for (const arg of options.args) {
|
|
if (dangerousArgs.some(dangerous => arg.includes(dangerous))) {
|
|
throw new Error(`Security violation: dangerous argument not allowed: ${arg}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create secure browser context with authentication and security controls
|
|
* @param {string} browserId - Browser ID
|
|
* @param {Object} contextOptions - Context options
|
|
* @returns {Promise<Object>} Secure context with authentication helpers
|
|
*/
|
|
async createSecureContext(browserId, contextOptions = {}) {
|
|
const browserInfo = this.activeBrowsers.get(browserId);
|
|
if (!browserInfo) {
|
|
throw new Error('Browser not found');
|
|
}
|
|
|
|
// Build secure context options
|
|
const secureContextOptions = this.buildSecureContextOptions(contextOptions);
|
|
|
|
// Create context
|
|
const context = await browserInfo.browser.newContext(secureContextOptions);
|
|
const contextId = this.generateContextId();
|
|
|
|
// Store context reference
|
|
this.activeContexts.set(contextId, {
|
|
context,
|
|
browserId,
|
|
created: new Date().toISOString(),
|
|
options: secureContextOptions
|
|
});
|
|
|
|
// Set up security monitoring
|
|
await this.setupSecurityMonitoring(context, contextId);
|
|
|
|
// Create authentication helpers
|
|
const authHelpers = this.createAuthenticationHelpers(context, contextId);
|
|
|
|
await this.credentialManager.auditLogger('BROWSER_CONTEXT_CREATED', {
|
|
contextId,
|
|
browserId,
|
|
securityFeatures: this.getContextSecurityFeatures(secureContextOptions)
|
|
});
|
|
|
|
return {
|
|
context,
|
|
contextId,
|
|
...authHelpers,
|
|
createSecurePage: () => this.createSecurePage(contextId)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Build secure context options
|
|
* @param {Object} userOptions - User context options
|
|
* @returns {Object} Secure context options
|
|
*/
|
|
buildSecureContextOptions(userOptions) {
|
|
const baseOptions = {
|
|
viewport: { width: 1280, height: 720 },
|
|
userAgent: 'HVAC-Testing-Framework/1.0 (Security-Hardened)',
|
|
ignoreHTTPSErrors: this.securityConfig.ignoreCertificateErrors,
|
|
|
|
// Permissions and security
|
|
permissions: [], // No permissions by default
|
|
geolocation: undefined, // No geolocation
|
|
locale: 'en-US',
|
|
|
|
// Recording options (secure)
|
|
recordVideo: undefined, // No video recording for security
|
|
recordHar: this.securityConfig.enableNetworkTracing ? {
|
|
mode: 'minimal',
|
|
content: 'omit' // Don't record response bodies
|
|
} : undefined
|
|
};
|
|
|
|
// Add base URL from secure configuration
|
|
baseOptions.baseURL = this.credentialManager.getBaseUrl();
|
|
|
|
return { ...baseOptions, ...userOptions };
|
|
}
|
|
|
|
/**
|
|
* Set up security monitoring for browser context
|
|
* @param {Object} context - Browser context
|
|
* @param {string} contextId - Context ID
|
|
*/
|
|
async setupSecurityMonitoring(context, contextId) {
|
|
// Monitor network requests for security
|
|
context.on('request', async (request) => {
|
|
const url = request.url();
|
|
|
|
// Block dangerous requests
|
|
if (this.isBlockedRequest(url)) {
|
|
await request.abort('blockedbyclient');
|
|
await this.credentialManager.auditLogger('REQUEST_BLOCKED', {
|
|
contextId,
|
|
url,
|
|
reason: 'security_policy'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Log sensitive requests
|
|
if (this.isSensitiveRequest(url)) {
|
|
await this.credentialManager.auditLogger('SENSITIVE_REQUEST', {
|
|
contextId,
|
|
url: this.sanitizeUrlForLogging(url),
|
|
method: request.method()
|
|
});
|
|
}
|
|
});
|
|
|
|
// Monitor responses for errors
|
|
context.on('response', async (response) => {
|
|
if (response.status() >= 400) {
|
|
await this.credentialManager.auditLogger('HTTP_ERROR', {
|
|
contextId,
|
|
url: this.sanitizeUrlForLogging(response.url()),
|
|
status: response.status()
|
|
});
|
|
}
|
|
});
|
|
|
|
// Monitor console messages for security issues
|
|
context.on('console', async (message) => {
|
|
const text = message.text();
|
|
if (this.isSecurityRelevantConsoleMessage(text)) {
|
|
await this.credentialManager.auditLogger('SECURITY_CONSOLE_MESSAGE', {
|
|
contextId,
|
|
type: message.type(),
|
|
message: this.sanitizeConsoleMessage(text)
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create authentication helpers for secure login
|
|
* @param {Object} context - Browser context
|
|
* @param {string} contextId - Context ID
|
|
* @returns {Object} Authentication helper methods
|
|
*/
|
|
createAuthenticationHelpers(context, contextId) {
|
|
return {
|
|
/**
|
|
* Authenticate as a specific user role
|
|
* @param {string} role - User role
|
|
* @returns {Promise<Object>} Authentication result
|
|
*/
|
|
authenticateAs: async (role) => {
|
|
const session = this.credentialManager.createSecureSession(role);
|
|
const credentials = this.credentialManager.getSessionCredentials(session.sessionId);
|
|
|
|
const page = await context.newPage();
|
|
|
|
try {
|
|
// Navigate to login page
|
|
const loginUrl = `${this.credentialManager.getBaseUrl()}/training-login/`;
|
|
await page.goto(loginUrl, { waitUntil: 'networkidle' });
|
|
|
|
// Perform secure login
|
|
await this.performSecureLogin(page, credentials, contextId);
|
|
|
|
// Verify authentication
|
|
await this.verifyAuthentication(page, credentials.role, contextId);
|
|
|
|
await this.credentialManager.auditLogger('AUTHENTICATION_SUCCESS', {
|
|
contextId,
|
|
role: credentials.role,
|
|
sessionId: session.sessionId
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
role: credentials.role,
|
|
sessionId: session.sessionId,
|
|
page
|
|
};
|
|
} catch (error) {
|
|
await page.close();
|
|
this.credentialManager.destroySession(session.sessionId);
|
|
|
|
await this.credentialManager.auditLogger('AUTHENTICATION_FAILED', {
|
|
contextId,
|
|
role,
|
|
error: error.message
|
|
});
|
|
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Logout and clean up session
|
|
* @param {string} sessionId - Session ID to destroy
|
|
* @returns {Promise<void>}
|
|
*/
|
|
logout: async (sessionId) => {
|
|
if (sessionId) {
|
|
this.credentialManager.destroySession(sessionId);
|
|
await this.credentialManager.auditLogger('LOGOUT_COMPLETED', {
|
|
contextId,
|
|
sessionId
|
|
});
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Perform secure login with credential validation
|
|
* @param {Object} page - Playwright page
|
|
* @param {Object} credentials - User credentials
|
|
* @param {string} contextId - Context ID
|
|
*/
|
|
async performSecureLogin(page, credentials, contextId) {
|
|
// Look for login form elements
|
|
const usernameSelectors = [
|
|
'input[name="log"]',
|
|
'input[name="username"]',
|
|
'#user_login',
|
|
'#username'
|
|
];
|
|
|
|
const passwordSelectors = [
|
|
'input[name="pwd"]',
|
|
'input[name="password"]',
|
|
'#user_pass',
|
|
'#password'
|
|
];
|
|
|
|
// Find and fill username
|
|
let usernameField = null;
|
|
for (const selector of usernameSelectors) {
|
|
try {
|
|
usernameField = page.locator(selector);
|
|
if (await usernameField.count() > 0) {
|
|
await usernameField.fill(credentials.username);
|
|
break;
|
|
}
|
|
} catch (e) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!usernameField || await usernameField.count() === 0) {
|
|
throw new Error('Username field not found');
|
|
}
|
|
|
|
// Find and fill password
|
|
let passwordField = null;
|
|
for (const selector of passwordSelectors) {
|
|
try {
|
|
passwordField = page.locator(selector);
|
|
if (await passwordField.count() > 0) {
|
|
await passwordField.fill(credentials.password);
|
|
break;
|
|
}
|
|
} catch (e) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!passwordField || await passwordField.count() === 0) {
|
|
throw new Error('Password field not found');
|
|
}
|
|
|
|
// Submit login form
|
|
const submitSelectors = [
|
|
'button[type="submit"]',
|
|
'input[type="submit"]',
|
|
'#wp-submit',
|
|
'.wp-submit'
|
|
];
|
|
|
|
let submitted = false;
|
|
for (const selector of submitSelectors) {
|
|
try {
|
|
const submitButton = page.locator(selector);
|
|
if (await submitButton.count() > 0) {
|
|
await submitButton.click();
|
|
submitted = true;
|
|
break;
|
|
}
|
|
} catch (e) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!submitted) {
|
|
throw new Error('Submit button not found');
|
|
}
|
|
|
|
// Wait for navigation
|
|
await page.waitForLoadState('networkidle', { timeout: 15000 });
|
|
}
|
|
|
|
/**
|
|
* Verify successful authentication
|
|
* @param {Object} page - Playwright page
|
|
* @param {string} expectedRole - Expected user role
|
|
* @param {string} contextId - Context ID
|
|
*/
|
|
async verifyAuthentication(page, expectedRole, contextId) {
|
|
// Check for authentication success indicators
|
|
const currentUrl = page.url();
|
|
|
|
// Should not be on login page after successful authentication
|
|
if (currentUrl.includes('/training-login/') || currentUrl.includes('/wp-login.php')) {
|
|
throw new Error('Authentication failed - still on login page');
|
|
}
|
|
|
|
// Role-specific URL verification
|
|
const roleUrlPatterns = {
|
|
'hvac_master_trainer': /\/master-trainer\//,
|
|
'hvac_trainer': /\/trainer\//,
|
|
'administrator': /\/wp-admin\//
|
|
};
|
|
|
|
if (roleUrlPatterns[expectedRole] && !roleUrlPatterns[expectedRole].test(currentUrl)) {
|
|
console.warn(`Authentication successful but unexpected URL pattern for role ${expectedRole}: ${currentUrl}`);
|
|
}
|
|
|
|
// Additional verification by checking page content
|
|
const bodyText = await page.textContent('body');
|
|
|
|
// Should not contain login-related error messages
|
|
const errorIndicators = [
|
|
'invalid username',
|
|
'invalid password',
|
|
'login failed',
|
|
'authentication error'
|
|
];
|
|
|
|
for (const indicator of errorIndicators) {
|
|
if (bodyText.toLowerCase().includes(indicator)) {
|
|
throw new Error(`Authentication failed: ${indicator} detected`);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create secure page with monitoring and security controls
|
|
* @param {string} contextId - Context ID
|
|
* @returns {Promise<Object>} Secure page with helpers
|
|
*/
|
|
async createSecurePage(contextId) {
|
|
const contextInfo = this.activeContexts.get(contextId);
|
|
if (!contextInfo) {
|
|
throw new Error('Context not found');
|
|
}
|
|
|
|
const page = await contextInfo.context.newPage();
|
|
|
|
// Set security headers
|
|
await page.setExtraHTTPHeaders({
|
|
'X-Security-Test': 'HVAC-Framework',
|
|
'Cache-Control': 'no-cache, no-store'
|
|
});
|
|
|
|
return {
|
|
page,
|
|
secureGoto: (url, options = {}) => this.securePageGoto(page, url, options, contextId),
|
|
secureScreenshot: (options = {}) => this.secureScreenshot(page, options, contextId)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Secure page navigation with URL validation
|
|
* @param {Object} page - Playwright page
|
|
* @param {string} url - URL to navigate to
|
|
* @param {Object} options - Navigation options
|
|
* @param {string} contextId - Context ID
|
|
*/
|
|
async securePageGoto(page, url, options, contextId) {
|
|
// Validate URL
|
|
if (!this.isAllowedUrl(url)) {
|
|
throw new Error(`URL not allowed: ${url}`);
|
|
}
|
|
|
|
// Navigate with security monitoring
|
|
const response = await page.goto(url, {
|
|
waitUntil: 'networkidle',
|
|
timeout: this.securityConfig.timeout,
|
|
...options
|
|
});
|
|
|
|
// Log navigation
|
|
await this.credentialManager.auditLogger('PAGE_NAVIGATION', {
|
|
contextId,
|
|
url: this.sanitizeUrlForLogging(url),
|
|
status: response ? response.status() : 'unknown'
|
|
});
|
|
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* Take secure screenshot with metadata
|
|
* @param {Object} page - Playwright page
|
|
* @param {Object} options - Screenshot options
|
|
* @param {string} contextId - Context ID
|
|
*/
|
|
async secureScreenshot(page, options, contextId) {
|
|
const filename = options.path ||
|
|
path.join(this.securityConfig.screenshotsDir, `screenshot-${contextId}-${Date.now()}.png`);
|
|
|
|
// Ensure screenshots directory exists
|
|
await fs.mkdir(path.dirname(filename), { recursive: true });
|
|
|
|
const screenshot = await page.screenshot({
|
|
fullPage: true,
|
|
...options,
|
|
path: filename
|
|
});
|
|
|
|
await this.credentialManager.auditLogger('SCREENSHOT_TAKEN', {
|
|
contextId,
|
|
filename,
|
|
size: screenshot.length
|
|
});
|
|
|
|
return screenshot;
|
|
}
|
|
|
|
/**
|
|
* Utility methods for security validation
|
|
*/
|
|
|
|
generateBrowserId() {
|
|
return `browser-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
}
|
|
|
|
generateContextId() {
|
|
return `context-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
}
|
|
|
|
isBlockedRequest(url) {
|
|
const blockedPatterns = [
|
|
/facebook\.com/,
|
|
/google-analytics\.com/,
|
|
/googletagmanager\.com/,
|
|
/doubleclick\.net/,
|
|
/\.ads\./
|
|
];
|
|
|
|
return blockedPatterns.some(pattern => pattern.test(url));
|
|
}
|
|
|
|
isSensitiveRequest(url) {
|
|
return url.includes('login') || url.includes('auth') || url.includes('password');
|
|
}
|
|
|
|
isSecurityRelevantConsoleMessage(message) {
|
|
const securityKeywords = [
|
|
'security', 'error', 'warning', 'blocked', 'cors', 'csp',
|
|
'mixed content', 'certificate', 'ssl', 'tls'
|
|
];
|
|
|
|
const lowerMessage = message.toLowerCase();
|
|
return securityKeywords.some(keyword => lowerMessage.includes(keyword));
|
|
}
|
|
|
|
isAllowedUrl(url) {
|
|
const baseUrl = this.credentialManager.getBaseUrl();
|
|
return url.startsWith(baseUrl) || url.startsWith('data:') || url.startsWith('about:');
|
|
}
|
|
|
|
sanitizeUrlForLogging(url) {
|
|
return url.replace(/[?&](password|pwd|token|key)=[^&]+/gi, '$1=***');
|
|
}
|
|
|
|
sanitizeConsoleMessage(message) {
|
|
return message.replace(/password[=:]\s*[^\s]+/gi, 'password=***')
|
|
.replace(/token[=:]\s*[^\s]+/gi, 'token=***');
|
|
}
|
|
|
|
getSecurityFeaturesSummary(options) {
|
|
return {
|
|
headless: options.headless,
|
|
sandboxEnabled: !options.args?.includes('--no-sandbox'),
|
|
tlsValidation: !options.ignoreHTTPSErrors
|
|
};
|
|
}
|
|
|
|
getContextSecurityFeatures(options) {
|
|
return {
|
|
httpsOnly: !options.ignoreHTTPSErrors,
|
|
permissionsLimited: options.permissions?.length === 0,
|
|
networkMonitoring: !!options.recordHar
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Clean up all browsers and contexts
|
|
*/
|
|
async cleanup() {
|
|
for (const [contextId, contextInfo] of this.activeContexts) {
|
|
try {
|
|
await contextInfo.context.close();
|
|
} catch (error) {
|
|
console.warn(`Failed to close context ${contextId}:`, error.message);
|
|
}
|
|
}
|
|
this.activeContexts.clear();
|
|
|
|
for (const [browserId, browserInfo] of this.activeBrowsers) {
|
|
try {
|
|
await browserInfo.browser.close();
|
|
} catch (error) {
|
|
console.warn(`Failed to close browser ${browserId}:`, error.message);
|
|
}
|
|
}
|
|
this.activeBrowsers.clear();
|
|
|
|
await this.credentialManager.auditLogger('BROWSER_CLEANUP_COMPLETED');
|
|
}
|
|
}
|
|
|
|
// Singleton instance
|
|
let browserManagerInstance = null;
|
|
|
|
/**
|
|
* Get singleton instance of SecureBrowserManager
|
|
* @returns {SecureBrowserManager}
|
|
*/
|
|
function getBrowserManager() {
|
|
if (!browserManagerInstance) {
|
|
browserManagerInstance = new SecureBrowserManager();
|
|
}
|
|
return browserManagerInstance;
|
|
}
|
|
|
|
module.exports = {
|
|
SecureBrowserManager,
|
|
getBrowserManager
|
|
}; |