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('; ');
}
}