Analyzing Report-Only Data

Analyzing Report-Only Data

Effective analysis of Report-Only data is crucial for creating accurate and functional CSP policies:

// CSP Report Analyzer
class CSPReportAnalyzer {
  constructor() {
    this.reports = [];
    this.analysis = {
      byDirective: {},
      bySource: {},
      byPage: {},
      timeline: []
    };
  }
  
  addReport(report) {
    this.reports.push(report);
    this.updateAnalysis(report);
  }
  
  updateAnalysis(report) {
    const violation = report['csp-report'];
    
    // Analyze by directive
    const directive = violation['violated-directive'];
    if (!this.analysis.byDirective[directive]) {
      this.analysis.byDirective[directive] = {
        count: 0,
        sources: new Set(),
        samples: []
      };
    }
    
    this.analysis.byDirective[directive].count++;
    this.analysis.byDirective[directive].sources.add(violation['blocked-uri']);
    if (this.analysis.byDirective[directive].samples.length < 10) {
      this.analysis.byDirective[directive].samples.push(violation);
    }
    
    // Analyze by source
    const source = violation['blocked-uri'];
    if (!this.analysis.bySource[source]) {
      this.analysis.bySource[source] = {
        count: 0,
        directives: new Set(),
        pages: new Set()
      };
    }
    
    this.analysis.bySource[source].count++;
    this.analysis.bySource[source].directives.add(directive);
    this.analysis.bySource[source].pages.add(violation['document-uri']);
    
    // Timeline analysis
    this.analysis.timeline.push({
      time: new Date(report.timestamp),
      directive: directive,
      source: source
    });
  }
  
  generateReport() {
    return {
      summary: this.generateSummary(),
      recommendations: this.generateRecommendations(),
      proposedPolicy: this.generateProposedPolicy(),
      riskAssessment: this.assessRisks()
    };
  }
  
  generateSummary() {
    const totalViolations = this.reports.length;
    const uniqueSources = new Set(
      this.reports.map(r => r['csp-report']['blocked-uri'])
    ).size;
    
    const topViolations = Object.entries(this.analysis.byDirective)
      .sort(([,a], [,b]) => b.count - a.count)
      .slice(0, 5)
      .map(([directive, data]) => ({
        directive,
        count: data.count,
        percentage: (data.count / totalViolations * 100).toFixed(2)
      }));
    
    return {
      totalViolations,
      uniqueSources,
      reportingPeriod: this.getReportingPeriod(),
      topViolations,
      averageViolationsPerDay: this.calculateDailyAverage()
    };
  }
  
  generateRecommendations() {
    const recommendations = [];
    
    // Check for legitimate services
    const legitimateServices = {
      'https://www.google-analytics.com': 'Google Analytics',
      'https://fonts.googleapis.com': 'Google Fonts',
      'https://cdnjs.cloudflare.com': 'CDNJS',
      'https://unpkg.com': 'UNPKG CDN'
    };
    
    Object.entries(this.analysis.bySource).forEach(([source, data]) => {
      if (legitimateServices[source] && data.count > 10) {
        recommendations.push({
          priority: 'high',
          action: 'add-to-policy',
          source: source,
          service: legitimateServices[source],
          directives: Array.from(data.directives),
          justification: `${data.count} violations from legitimate service`
        });
      }
    });
    
    // Check for inline script/style issues
    if (this.analysis.bySource['inline'] && this.analysis.bySource['inline'].count > 50) {
      recommendations.push({
        priority: 'medium',
        action: 'refactor-inline-code',
        details: 'High number of inline script violations',
        suggestion: 'Consider using nonces or moving inline code to external files'
      });
    }
    
    return recommendations;
  }
  
  generateProposedPolicy() {
    const policy = {
      'default-src': ["'self'"],
      'script-src': ["'self'"],
      'style-src': ["'self'"],
      'img-src': ["'self'"],
      'font-src': ["'self'"],
      'connect-src': ["'self'"]
    };
    
    // Add sources based on violation analysis
    this.recommendations.forEach(rec => {
      if (rec.action === 'add-to-policy') {
        rec.directives.forEach(directive => {
          if (!policy[directive]) {
            policy[directive] = ["'self'"];
          }
          policy[directive].push(rec.source);
        });
      }
    });
    
    // Format as CSP string
    return Object.entries(policy)
      .map(([directive, sources]) => 
        `${directive} ${sources.join(' ')}`
      )
      .join('; ');
  }
}