Skip to main content

CSP Reporting: Monitor React Security Violations

CSP violation reports are JSON-formatted alerts sent to a server when the browser blocks a resource. By collecting these reports, you can detect XSS attempts in real time, catch misconfigurations (legitimate resources incorrectly blocked), and measure CSP effectiveness. Modern browsers support two reporting mechanisms: the older report-uri directive and the newer report-to (Reporting API). This article teaches you how to set up both, parse violation reports, and act on them in your React application's monitoring infrastructure.

CSP violation reporting is recommended for all production applications (estimated 23% of production sites monitor CSP in 2026, up from 8% in 2023).

What a CSP Violation Report Contains

When the browser blocks a resource due to CSP, it sends a report to your server containing:

{
"csp-report": {
"document-uri": "https://example.com/dashboard",
"violated-directive": "script-src",
"effective-directive": "script-src",
"original-policy": "default-src 'self'; script-src 'self' 'nonce-abc123'",
"disposition": "enforce",
"blocked-uri": "https://evil.com/malicious.js",
"source-file": "https://example.com/app.js",
"line-number": 42,
"column-number": 5,
"status-code": 200
}
}

Each field tells you what was blocked and where:

  • document-uri: The page that triggered the violation.
  • violated-directive: The CSP directive that was violated (e.g., script-src).
  • blocked-uri: The resource the browser tried to block.
  • source-file: The file that attempted to load the resource (if inline or in a script).
  • line-number and column-number: Where in the code the violation occurred.

Setting Up report-uri (Legacy but Widely Supported)

The report-uri directive tells the browser where to send violation reports:

Content-Security-Policy: default-src 'self'; script-src 'self'; report-uri https://example.com/csp-report

When the browser blocks a resource, it sends a POST request to https://example.com/csp-report with the violation report as JSON.

Server endpoint to receive reports:

// Node.js/Express
const express = require('express');
const app = express();

app.use(express.json());

// Endpoint to receive CSP violation reports
app.post('/csp-report', (req, res) => {
const report = req.body['csp-report'];

if (!report) {
return res.status(400).json({ error: 'No CSP report in body' });
}

console.log('CSP Violation detected:');
console.log(` Document: ${report['document-uri']}`);
console.log(` Blocked URI: ${report['blocked-uri']}`);
console.log(` Violated Directive: ${report['violated-directive']}`);
console.log(` Original Policy: ${report['original-policy']}`);

// Step 1: Log to your monitoring system
logToMonitoring({
type: 'csp-violation',
documentUri: report['document-uri'],
blockedUri: report['blocked-uri'],
violatedDirective: report['violated-directive'],
sourceFile: report['source-file'],
lineNumber: report['line-number'],
timestamp: new Date().toISOString(),
});

// Step 2: Check if this looks like an attack
if (isLikelyAttack(report)) {
alertSecurityTeam(report);
}

// Respond with 204 No Content (standard for CSP reports)
res.status(204).send();
});

function isLikelyAttack(report) {
const blockedUri = report['blocked-uri'];

// Red flags
const suspiciousDomains = [
'evil.com',
'attacker.com',
'malicious.co',
];

// Check if blocked URI contains suspiciously named domain or obfuscated code
return suspiciousDomains.some(domain => blockedUri.includes(domain)) ||
blockedUri.includes('data:') && blockedUri.includes('script');
}

function logToMonitoring(data) {
// Send to your monitoring/analytics service
// E.g., Sentry, Datadog, CloudWatch, etc.
console.log('Logged to monitoring:', data);
}

function alertSecurityTeam(report) {
// Send an alert email or Slack message to your security team
console.warn('POTENTIAL SECURITY INCIDENT:', report);
}

app.listen(3001);

Setting Up report-to (Reporting API)

The Reporting-Endpoints header and report-to directive are the modern approach:

Reporting-Endpoints: csp-endpoint="https://example.com/csp-report"
Content-Security-Policy: default-src 'self'; script-src 'self'; report-to csp-endpoint

The report-to directive is more flexible; you can define multiple endpoints and choose which violation types go to which endpoint.

Server setup with Reporting API:

// Node.js/Express
const express = require('express');
const app = express();
app.use(express.json());

// Set Reporting-Endpoints header
app.use((req, res, next) => {
res.setHeader(
'Reporting-Endpoints',
'csp-endpoint="https://example.com/csp-report"'
);
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; script-src 'self'; report-to csp-endpoint"
);
next();
});

// Handle Reporting API reports
app.post('/csp-report', (req, res) => {
// Reporting API sends an array of reports
const reports = req.body || [];

reports.forEach(report => {
if (report.type === 'csp-violation') {
const body = report.body;
console.log('CSP Violation via Reporting API:');
console.log(` Document: ${body['document-uri']}`);
console.log(` Blocked: ${body['blocked-uri']}`);

logToMonitoring({
type: 'csp-violation',
documentUri: body['document-uri'],
blockedUri: body['blocked-uri'],
});
}
});

res.status(204).send();
});

app.listen(3001);

Monitoring CSP Violations in Your React App

You can also listen for CSP violations in your React component and send them directly to your analytics service:

// React component or global effect
import React, { useEffect } from 'react';

export default function SecurityMonitor() {
useEffect(() => {
// Listen for securitypolicyviolation events
const handleViolation = (event) => {
console.warn('CSP violation detected:', {
blockedUri: event.blockedURI,
violatedDirective: event.violatedDirective,
originalPolicy: event.originalPolicy,
});

// Send to your monitoring backend
fetch('/api/log-csp-violation', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
blockedUri: event.blockedURI,
violatedDirective: event.violatedDirective,
sourceFile: event.sourceFile,
lineNumber: event.lineNumber,
originalPolicy: event.originalPolicy,
timestamp: new Date().toISOString(),
}),
}).catch(err => console.error('Failed to log CSP violation:', err));
};

document.addEventListener('securitypolicyviolation', handleViolation);

return () => {
document.removeEventListener('securitypolicyviolation', handleViolation);
};
}, []);

return null; // This component renders nothing
}

Add this component to your React app root to monitor all violations globally:

// App.js
import React from 'react';
import SecurityMonitor from './SecurityMonitor';

function App() {
return (
<>
<SecurityMonitor />
{/* rest of your app */}
</>
);
}

export default App;

Parsing and Acting on Violations

Once you are collecting reports, you can act on them:

Example: Whitelist a legitimate blocked resource:

If you see legitimate resources being blocked (e.g., fonts.googleapis.com), add them to your CSP:

script-src 'self' fonts.googleapis.com

Regenerate your CSP header on the server and redeploy.

Example: Detect and alert on attack patterns:

function analyzeViolation(report) {
const blockedUri = report['blocked-uri'];

// Pattern 1: Obfuscated code
if (blockedUri.startsWith('data:')) {
return { severity: 'high', reason: 'Data URI script injection attempt' };
}

// Pattern 2: External domain loading script
if (blockedUri.includes('http') && !isAllowedDomain(blockedUri)) {
return { severity: 'medium', reason: 'Unexpected external script' };
}

// Pattern 3: Multiple violations from same source
// (requires log aggregation)

return { severity: 'low', reason: 'Unknown resource blocked' };
}

function isAllowedDomain(uri) {
const allowed = ['cdn.example.com', 'fonts.googleapis.com'];
return allowed.some(domain => uri.includes(domain));
}

Key Takeaways

  • CSP violation reports are JSON payloads sent to your server when the browser blocks a resource.
  • Use report-uri for broad browser support or report-to (Reporting API) for modern browsers.
  • Collect reports to detect XSS attempts, catch misconfigurations, and measure CSP effectiveness.
  • Listen for securitypolicyviolation events in your React app for real-time client-side monitoring.
  • Analyze reports to distinguish attacks from legitimate blocks and adjust your CSP accordingly.

Frequently Asked Questions

Should I use report-uri or report-to?

Use both for broad compatibility. Older browsers support report-uri; modern browsers support report-to. The two can coexist in the same CSP header.

What if I get a report for a resource I cannot control (e.g., a third-party widget)?

Whitelist that domain in your CSP if it is legitimate. For example, if Stripe.js is blocked, add script-src 'self' https://js.stripe.com. However, validate that the domain is official (check the third party's documentation).

Can an attacker spam my report endpoint with fake CSP reports?

Technically yes, but unlikely. CSP reports are sent by the browser, not JavaScript, and only for actual violations. An attacker would need to trick the browser into blocking resources by injecting a malicious CSP header (requires server-side access).

How long should I retain CSP reports?

Retention depends on your compliance requirements. For breach investigation, 90 days is common. For trend analysis, 1 year is reasonable. Store in your logging/monitoring service (Sentry, Datadog, CloudWatch, etc.).

Further Reading