Implementing Report-Only Mode
Implementing Report-Only Mode
Setting up Report-Only mode requires careful configuration of headers and violation reporting infrastructure:
// Express.js Report-Only Implementation
const express = require('express');
const crypto = require('crypto');
class CSPReportOnly {
constructor(app) {
this.app = app;
this.violations = [];
this.setupMiddleware();
this.setupReportingEndpoint();
}
setupMiddleware() {
this.app.use((req, res, next) => {
// Generate nonce for this request
const nonce = crypto.randomBytes(16).toString('base64');
res.locals.cspNonce = nonce;
// Build report-only policy
const policy = this.buildPolicy(nonce);
res.setHeader('Content-Security-Policy-Report-Only', policy);
// Also set reporting endpoint headers
res.setHeader('Report-To', JSON.stringify({
group: 'csp-endpoint',
max_age: 10886400,
endpoints: [{
url: '/api/csp-reports'
}]
}));
next();
});
}
buildPolicy(nonce) {
const directives = [
"default-src 'self'",
`script-src 'self' 'nonce-${nonce}' https://cdn.jsdelivr.net https://unpkg.com`,
`style-src 'self' 'nonce-${nonce}' https://fonts.googleapis.com`,
"img-src 'self' data: https:",
"font-src 'self' https://fonts.gstatic.com",
"connect-src 'self' https://api.example.com wss://realtime.example.com",
"frame-ancestors 'self'",
"base-uri 'self'",
"form-action 'self'",
"report-uri /api/csp-reports",
"report-to csp-endpoint"
];
return directives.join('; ');
}
setupReportingEndpoint() {
// Traditional report-uri endpoint
this.app.post('/api/csp-reports',
express.json({ type: 'application/csp-report' }),
(req, res) => {
this.handleViolationReport(req.body);
res.status(204).end();
}
);
// Modern Report-To API endpoint
this.app.post('/api/csp-reports',
express.json({ type: 'application/reports+json' }),
(req, res) => {
req.body.forEach(report => {
if (report.type === 'csp-violation') {
this.handleViolationReport(report.body);
}
});
res.status(204).end();
}
);
}
handleViolationReport(report) {
const violation = report['csp-report'] || report;
// Enrich violation data
const enrichedViolation = {
...violation,
timestamp: new Date().toISOString(),
userAgent: violation['user-agent'],
violationType: this.categorizeViolation(violation)
};
this.violations.push(enrichedViolation);
this.analyzeViolation(enrichedViolation);
}
categorizeViolation(violation) {
const blockedUri = violation['blocked-uri'] || '';
const violatedDirective = violation['violated-directive'] || '';
if (blockedUri === 'inline') return 'inline-script';
if (blockedUri === 'eval') return 'unsafe-eval';
if (blockedUri.startsWith('data:')) return 'data-uri';
if (violatedDirective.includes('frame-ancestors')) return 'framing-attempt';
return 'external-resource';
}
}