UNPKG

@casoon/auditmysite

Version:

Professional website analysis suite with robust accessibility testing, Core Web Vitals performance monitoring, SEO analysis, and content optimization insights. Features isolated browser contexts, retry mechanisms, and comprehensive API endpoints for profe

736 lines 28.7 kB
"use strict"; /** * 🔐 Security Headers Analyzer * * Comprehensive security headers analysis including: * - Content Security Policy (CSP) validation * - HTTP Strict Transport Security (HSTS) * - X-Frame-Options, X-Content-Type-Options * - Referrer Policy, Permissions Policy * - HTTPS configuration and certificate analysis * - Cookie security analysis */ Object.defineProperty(exports, "__esModule", { value: true }); exports.SecurityHeadersAnalyzer = void 0; const base_types_1 = require("../types/base-types"); class SecurityHeadersAnalyzer { constructor() { } // BaseAnalyzer interface implementations getName() { return 'SecurityHeadersAnalyzer'; } getVersion() { return '1.0.0'; } getScore(result) { return result.overallScore; } getGrade(score) { return (0, base_types_1.calculateGrade)(score); } getCertificateLevel(score) { return (0, base_types_1.calculateCertificateLevel)(score); } getRecommendations(result) { return result.recommendations; } /** * Main analyze method implementing BaseAnalyzer interface */ async analyze(page, url, options = {}) { // Extract URL string from URL object if needed const urlString = (typeof url === 'object' && url.loc ? url.loc : url); const startTime = Date.now(); try { // Capture initial page response for headers analysis let response = null; // Set up response listener before navigation page.once('response', (resp) => { if (resp.url() === urlString) { response = resp; } }); // If page is already loaded, try to get response from navigation try { // Check if page is already at the target URL const currentUrl = page.url(); if (currentUrl !== urlString) { response = await page.goto(urlString, { waitUntil: 'networkidle', timeout: options.timeout || 30000 }); } } catch (error) { console.warn('Failed to navigate for security headers analysis:', error); // Continue with analysis using current page state } // Analyze security headers const securityHeaders = await this.analyzeSecurityHeaders(page, urlString, response, options); const duration = Date.now() - startTime; // Calculate overall score const overallScore = this.calculateOverallScore(securityHeaders); const grade = (0, base_types_1.calculateGrade)(overallScore); const certificate = (0, base_types_1.calculateCertificateLevel)(overallScore); // Generate recommendations const recommendations = this.generateRecommendations(securityHeaders); return { overallScore, grade, certificate, analyzedAt: new Date().toISOString(), duration, status: 'completed', securityHeaders, recommendations }; } catch (error) { console.error('❌ Security headers analysis failed:', error); throw new Error(`Security headers analysis failed: ${error}`); } } /** * Analyze security headers comprehensively */ async analyzeSecurityHeaders(page, url, response, options) { const headers = response ? await response.headers() : {}; // Analyze individual security headers const csp = this.analyzeCSP(headers); const hsts = this.analyzeHSTS(headers); const xFrameOptions = this.analyzeXFrameOptions(headers); const xContentTypeOptions = this.analyzeXContentTypeOptions(headers); const xXSSProtection = this.analyzeXXSSProtection(headers); const referrerPolicy = this.analyzeReferrerPolicy(headers); const permissionsPolicy = this.analyzePermissionsPolicy(headers); // Analyze HTTPS configuration const https = await this.analyzeHTTPS(url, page, options); // Analyze cookies security const cookies = options.includeCookieAnalysis ? await this.analyzeCookieSecurity(page) : this.getDefaultCookieAnalysis(); // Assess vulnerabilities const vulnerabilities = this.assessVulnerabilities({ csp, hsts, xFrameOptions, xContentTypeOptions, https }); // Calculate overall score const overallScore = this.calculateSecurityScore({ csp, hsts, xFrameOptions, xContentTypeOptions, xXSSProtection, referrerPolicy, permissionsPolicy, https, cookies }); const securityGrade = (0, base_types_1.calculateGrade)(overallScore); // Generate security-specific recommendations const recommendations = this.generateSecurityRecommendations({ csp, hsts, xFrameOptions, xContentTypeOptions, xXSSProtection, referrerPolicy, permissionsPolicy, https, cookies, vulnerabilities }); return { overallScore, securityGrade, headers: { csp, hsts, xFrameOptions, xContentTypeOptions, xXSSProtection, referrerPolicy, permissionsPolicy }, https, cookies, recommendations, vulnerabilities }; } /** * Analyze Content Security Policy */ analyzeCSP(headers) { const cspHeader = headers['content-security-policy'] || headers['content-security-policy-report-only']; if (!cspHeader) { return { present: false, directives: {}, score: 0, issues: [{ severity: 'critical', issue: 'Content Security Policy is missing', recommendation: 'Implement a CSP header to prevent XSS attacks and data injection' }], hasUnsafeDirectives: false, allowsInlineScripts: true, // No CSP means everything is allowed allowsEval: true }; } // Parse CSP directives const directives = {}; const directiveStrings = cspHeader.split(';').map(d => d.trim()).filter(d => d); for (const directive of directiveStrings) { const [name, ...values] = directive.split(/\s+/); if (name) { directives[name] = values; } } // Analyze security implications const issues = []; let score = 100; // Check for unsafe directives const hasUnsafeInline = this.hasUnsafeInlineDirectives(directives); const hasUnsafeEval = this.hasUnsafeEvalDirectives(directives); const allowsInlineScripts = hasUnsafeInline || !directives['script-src']; const allowsEval = hasUnsafeEval || !directives['script-src']; if (hasUnsafeInline) { score -= 30; issues.push({ severity: 'high', directive: 'script-src or style-src', issue: "Unsafe 'unsafe-inline' directive found", recommendation: 'Remove unsafe-inline and use nonces or hashes for inline scripts/styles' }); } if (hasUnsafeEval) { score -= 25; issues.push({ severity: 'high', directive: 'script-src', issue: "Unsafe 'unsafe-eval' directive found", recommendation: 'Remove unsafe-eval to prevent code injection attacks' }); } // Check for missing important directives if (!directives['default-src']) { score -= 15; issues.push({ severity: 'medium', directive: 'default-src', issue: 'Missing default-src directive', recommendation: 'Add default-src directive as fallback for other resource types' }); } if (!directives['script-src']) { score -= 20; issues.push({ severity: 'medium', directive: 'script-src', issue: 'Missing script-src directive', recommendation: 'Add script-src directive to control JavaScript execution' }); } if (!directives['object-src']) { score -= 10; issues.push({ severity: 'low', directive: 'object-src', issue: 'Missing object-src directive', recommendation: "Add 'object-src none' to prevent plugin content" }); } return { present: true, value: cspHeader, directives, score: Math.max(0, score), issues, hasUnsafeDirectives: hasUnsafeInline || hasUnsafeEval, allowsInlineScripts, allowsEval }; } /** * Check for unsafe inline directives in CSP */ hasUnsafeInlineDirectives(directives) { const relevantDirectives = ['script-src', 'style-src', 'default-src']; return relevantDirectives.some(directive => directives[directive]?.includes("'unsafe-inline'")); } /** * Check for unsafe eval directives in CSP */ hasUnsafeEvalDirectives(directives) { const relevantDirectives = ['script-src', 'default-src']; return relevantDirectives.some(directive => directives[directive]?.includes("'unsafe-eval'")); } /** * Analyze HTTP Strict Transport Security */ analyzeHSTS(headers) { const hstsHeader = headers['strict-transport-security']; if (!hstsHeader) { return { name: 'Strict-Transport-Security', present: false, valid: false, score: 0, issues: ['HSTS header is missing - HTTPS connections not enforced'], recommendations: ['Add Strict-Transport-Security header to enforce HTTPS'] }; } let score = 100; const issues = []; const recommendations = []; // Parse max-age const maxAgeMatch = hstsHeader.match(/max-age=(\d+)/); const maxAge = maxAgeMatch ? parseInt(maxAgeMatch[1]) : 0; if (maxAge < 31536000) { // Less than 1 year score -= 20; issues.push(`Max-age is too short (${maxAge} seconds)`); recommendations.push('Set max-age to at least 31536000 (1 year)'); } // Check for includeSubDomains if (!hstsHeader.includes('includeSubDomains')) { score -= 10; issues.push('includeSubDomains directive is missing'); recommendations.push('Add includeSubDomains to protect subdomains'); } // Check for preload if (!hstsHeader.includes('preload')) { score -= 5; recommendations.push('Consider adding preload directive for HSTS preload list'); } return { name: 'Strict-Transport-Security', value: hstsHeader, present: true, valid: maxAge > 0, score: Math.max(0, score), issues, recommendations }; } /** * Analyze X-Frame-Options header */ analyzeXFrameOptions(headers) { const xFrameHeader = headers['x-frame-options']; if (!xFrameHeader) { return { name: 'X-Frame-Options', present: false, valid: false, score: 0, issues: ['X-Frame-Options header is missing - vulnerable to clickjacking'], recommendations: ['Add X-Frame-Options: DENY or SAMEORIGIN to prevent clickjacking'] }; } const value = xFrameHeader.toLowerCase(); const validValues = ['deny', 'sameorigin']; const isValid = validValues.includes(value) || value.startsWith('allow-from'); let score = isValid ? 100 : 0; const issues = []; const recommendations = []; if (!isValid) { issues.push(`Invalid X-Frame-Options value: ${xFrameHeader}`); recommendations.push('Use DENY, SAMEORIGIN, or ALLOW-FROM with a specific origin'); } else if (value === 'sameorigin') { score = 90; // DENY is more secure recommendations.push('Consider using DENY for maximum security if framing is not needed'); } return { name: 'X-Frame-Options', value: xFrameHeader, present: true, valid: isValid, score, issues, recommendations }; } /** * Analyze X-Content-Type-Options header */ analyzeXContentTypeOptions(headers) { const xContentTypeHeader = headers['x-content-type-options']; if (!xContentTypeHeader) { return { name: 'X-Content-Type-Options', present: false, valid: false, score: 0, issues: ['X-Content-Type-Options header is missing - vulnerable to MIME-type sniffing'], recommendations: ['Add X-Content-Type-Options: nosniff to prevent MIME-type sniffing'] }; } const isValid = xContentTypeHeader.toLowerCase() === 'nosniff'; return { name: 'X-Content-Type-Options', value: xContentTypeHeader, present: true, valid: isValid, score: isValid ? 100 : 0, issues: isValid ? [] : [`Invalid value: ${xContentTypeHeader}, should be 'nosniff'`], recommendations: isValid ? [] : ['Set X-Content-Type-Options to "nosniff"'] }; } /** * Analyze X-XSS-Protection header (deprecated but still relevant) */ analyzeXXSSProtection(headers) { const xssHeader = headers['x-xss-protection']; if (!xssHeader) { return { name: 'X-XSS-Protection', present: false, valid: false, score: 50, // Not critical since it's deprecated issues: ['X-XSS-Protection header is missing'], recommendations: ['Add X-XSS-Protection: 1; mode=block (though CSP is preferred)'] }; } const value = xssHeader.toLowerCase(); const isValid = value.includes('1') && value.includes('mode=block'); return { name: 'X-XSS-Protection', value: xssHeader, present: true, valid: isValid, score: isValid ? 100 : 70, issues: isValid ? [] : ['X-XSS-Protection should be "1; mode=block"'], recommendations: isValid ? ['Consider implementing CSP instead of relying on X-XSS-Protection'] : ['Set X-XSS-Protection to "1; mode=block"'] }; } /** * Analyze Referrer-Policy header */ analyzeReferrerPolicy(headers) { const referrerHeader = headers['referrer-policy']; if (!referrerHeader) { return { name: 'Referrer-Policy', present: false, valid: false, score: 70, // Not critical but recommended issues: ['Referrer-Policy header is missing'], recommendations: ['Add Referrer-Policy header to control referrer information'] }; } const validPolicies = [ 'no-referrer', 'no-referrer-when-downgrade', 'origin', 'origin-when-cross-origin', 'same-origin', 'strict-origin', 'strict-origin-when-cross-origin', 'unsafe-url' ]; const policy = referrerHeader.toLowerCase(); const isValid = validPolicies.includes(policy); let score = isValid ? 100 : 0; const recommendations = []; // Rate security level of different policies if (policy === 'no-referrer' || policy === 'strict-origin-when-cross-origin') { score = 100; } else if (policy === 'unsafe-url') { score = 30; recommendations.push('Consider using a more restrictive policy like "strict-origin-when-cross-origin"'); } return { name: 'Referrer-Policy', value: referrerHeader, present: true, valid: isValid, score, issues: isValid ? [] : [`Invalid referrer policy: ${referrerHeader}`], recommendations }; } /** * Analyze Permissions-Policy / Feature-Policy header */ analyzePermissionsPolicy(headers) { const permissionsHeader = headers['permissions-policy'] || headers['feature-policy']; if (!permissionsHeader) { return { name: 'Permissions-Policy', present: false, valid: false, score: 80, // Good to have but not critical issues: ['Permissions-Policy header is missing'], recommendations: ['Consider adding Permissions-Policy to control browser features'] }; } // Basic validation - permissions policy syntax is complex const hasValidSyntax = permissionsHeader.includes('=') || permissionsHeader.includes('()'); return { name: 'Permissions-Policy', value: permissionsHeader, present: true, valid: hasValidSyntax, score: hasValidSyntax ? 100 : 50, issues: hasValidSyntax ? [] : ['Permissions-Policy syntax appears invalid'], recommendations: hasValidSyntax ? ['Review permissions policy to ensure it matches your security requirements'] : ['Fix Permissions-Policy syntax and test with browser developer tools'] }; } /** * Analyze HTTPS configuration */ async analyzeHTTPS(url, page, options) { const isHTTPS = (0, base_types_1.isSecureUrl)(url); let httpsRedirect = false; let mixedContent = false; if (isHTTPS) { // Check for mixed content by analyzing page resources try { mixedContent = await page.evaluate(() => { const resources = Array.from(document.querySelectorAll('script[src], link[href], img[src], iframe[src]')); return resources.some(element => { const src = element.getAttribute('src') || element.getAttribute('href'); return src && src.startsWith('http://'); }); }); } catch (error) { console.warn('Failed to check for mixed content:', error); } } else { // Test if HTTP redirects to HTTPS try { const httpUrl = url.replace('https://', 'http://'); const response = await page.goto(httpUrl, { waitUntil: 'domcontentloaded', timeout: 5000 }); httpsRedirect = response?.url().startsWith('https://') || false; } catch (error) { // Redirect test failed, assume no redirect httpsRedirect = false; } } // Certificate analysis (basic - would need more sophisticated analysis for production) const certificate = { valid: isHTTPS, // Simplified - actual cert validation would require additional checks issuer: undefined, expiresAt: undefined, daysUntilExpiry: undefined }; return { enabled: isHTTPS, httpsRedirect, mixedContent, certificate }; } /** * Analyze cookie security */ async analyzeCookieSecurity(page) { try { const cookies = await page.context().cookies(); const totalCookies = cookies.length; const secureCookies = cookies.filter(c => c.secure).length; const httpOnlyCookies = cookies.filter(c => c.httpOnly).length; const sameSiteCookies = cookies.filter(c => c.sameSite && c.sameSite !== 'None').length; const issues = []; if (totalCookies > 0) { if (secureCookies < totalCookies) { issues.push(`${totalCookies - secureCookies} cookies without Secure flag`); } if (httpOnlyCookies < totalCookies) { issues.push(`${totalCookies - httpOnlyCookies} cookies without HttpOnly flag`); } if (sameSiteCookies < totalCookies) { issues.push(`${totalCookies - sameSiteCookies} cookies without proper SameSite attribute`); } } return { totalCookies, secureCookies, httpOnlyCookies, sameSiteCookies, issues }; } catch (error) { console.warn('Failed to analyze cookies:', error); return this.getDefaultCookieAnalysis(); } } /** * Get default cookie analysis when analysis fails */ getDefaultCookieAnalysis() { return { totalCookies: 0, secureCookies: 0, httpOnlyCookies: 0, sameSiteCookies: 0, issues: ['Could not analyze cookies'] }; } /** * Assess overall vulnerability status */ assessVulnerabilities(analysis) { return { clickjacking: analysis.xFrameOptions.valid ? 'protected' : 'vulnerable', xss: analysis.csp.present && !analysis.csp.hasUnsafeDirectives ? 'protected' : analysis.csp.present ? 'partially_protected' : 'vulnerable', contentTypeSniffing: analysis.xContentTypeOptions.valid ? 'protected' : 'vulnerable', referrerLeakage: 'partially_protected', // Depends on referrer policy mixedContent: analysis.https.enabled && !analysis.https.mixedContent ? 'protected' : 'vulnerable' }; } /** * Calculate overall security score */ calculateSecurityScore(analysis) { const weights = { csp: 0.25, // 25% - Most important hsts: 0.20, // 20% - Critical for HTTPS xFrameOptions: 0.15, // 15% - Clickjacking protection xContentTypeOptions: 0.15, // 15% - MIME sniffing protection https: 0.15, // 15% - HTTPS usage xXSSProtection: 0.05, // 5% - Deprecated but still counted referrerPolicy: 0.03, // 3% - Privacy protection permissionsPolicy: 0.02 // 2% - Feature control }; let weightedScore = 0; let totalWeight = 0; // CSP score weightedScore += analysis.csp.score * weights.csp; totalWeight += weights.csp; // HSTS score weightedScore += analysis.hsts.score * weights.hsts; totalWeight += weights.hsts; // X-Frame-Options score weightedScore += analysis.xFrameOptions.score * weights.xFrameOptions; totalWeight += weights.xFrameOptions; // X-Content-Type-Options score weightedScore += analysis.xContentTypeOptions.score * weights.xContentTypeOptions; totalWeight += weights.xContentTypeOptions; // HTTPS score const httpsScore = analysis.https.enabled ? 100 : 0; weightedScore += httpsScore * weights.https; totalWeight += weights.https; // Other headers weightedScore += analysis.xXSSProtection.score * weights.xXSSProtection; totalWeight += weights.xXSSProtection; weightedScore += analysis.referrerPolicy.score * weights.referrerPolicy; totalWeight += weights.referrerPolicy; weightedScore += analysis.permissionsPolicy.score * weights.permissionsPolicy; totalWeight += weights.permissionsPolicy; return Math.round(weightedScore / totalWeight); } /** * Calculate overall score for BaseAnalyzer interface */ calculateOverallScore(securityHeaders) { return securityHeaders.overallScore; } /** * Generate security-specific recommendations */ generateSecurityRecommendations(analysis) { const recommendations = []; // CSP recommendations if (!analysis.csp.present) { recommendations.push({ priority: 'critical', category: 'Content Security Policy', issue: 'No Content Security Policy implemented', recommendation: 'Implement a CSP header to prevent XSS attacks and data injection', impact: 'Protects against cross-site scripting and data injection attacks' }); } else if (analysis.csp.hasUnsafeDirectives) { recommendations.push({ priority: 'high', category: 'Content Security Policy', issue: 'CSP contains unsafe directives', recommendation: 'Remove unsafe-inline and unsafe-eval directives, use nonces or hashes instead', impact: 'Eliminates major XSS attack vectors' }); } // HSTS recommendations if (!analysis.hsts.present && analysis.https.enabled) { recommendations.push({ priority: 'high', category: 'HTTPS Security', issue: 'HSTS header missing', recommendation: 'Add Strict-Transport-Security header with includeSubDomains', impact: 'Prevents HTTPS downgrade attacks and protects subdomains' }); } // Clickjacking recommendations if (!analysis.xFrameOptions.present) { recommendations.push({ priority: 'medium', category: 'Clickjacking Protection', issue: 'X-Frame-Options header missing', recommendation: 'Add X-Frame-Options: DENY or SAMEORIGIN header', impact: 'Prevents clickjacking attacks' }); } // HTTPS recommendations if (!analysis.https.enabled) { recommendations.push({ priority: 'critical', category: 'HTTPS', issue: 'Site not using HTTPS', recommendation: 'Implement HTTPS with valid SSL certificate', impact: 'Encrypts data transmission and improves SEO ranking' }); } return recommendations; } /** * Generate general recommendations for BaseAnalyzer interface */ generateRecommendations(securityHeaders) { return securityHeaders.recommendations.map(rec => ({ id: `security-${rec.category.toLowerCase().replace(/\s+/g, '-')}`, priority: rec.priority, category: rec.category, issue: rec.issue, recommendation: rec.recommendation, impact: rec.impact, effort: this.estimateEffort(rec.priority), scoreImprovement: this.estimateScoreImprovement(rec.priority) })); } /** * Estimate implementation effort based on priority */ estimateEffort(priority) { switch (priority) { case 'critical': return 8; case 'high': return 4; case 'medium': return 2; case 'low': return 1; default: return 2; } } /** * Estimate score improvement based on priority */ estimateScoreImprovement(priority) { switch (priority) { case 'critical': return 25; case 'high': return 15; case 'medium': return 10; case 'low': return 5; default: return 8; } } } exports.SecurityHeadersAnalyzer = SecurityHeadersAnalyzer; //# sourceMappingURL=security-headers-analyzer.js.map