CSP for Sentry: Copy-Paste Policies and Debugging
Table of Contents
Sentry is usually the first thing that breaks when you tighten a Content Security Policy.
That makes sense. Sentry’s browser SDK wants to send network requests, sometimes open tunnels through your own domain, sometimes upload source maps in CI, and occasionally capture session replay data. A strict CSP blocks all of that unless you explicitly allow it.
If you just want the answer: for Sentry in the browser, connect-src is the directive that matters most.
The minimum CSP for Sentry
If your frontend app loads Sentry from your own bundled JavaScript, you usually do not need to allow Sentry in script-src. You only need to allow its ingestion endpoint in connect-src.
Basic example:
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
connect-src 'self' https://o0.ingest.sentry.io;
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
That works when:
- your app bundles
@sentry/browserlocally - events are sent directly to
https://o0.ingest.sentry.ioor a similar ingest host - you are not using replay, custom tunnels, or CDN-hosted Sentry assets
How to find the right Sentry endpoint
Sentry DSNs look like this:
https://[email protected]/1234567
From that DSN, the CSP-relevant host is:
https://o0.ingest.sentry.io
That host must be present in connect-src.
Another example:
https://[email protected]/987654
Then your CSP needs:
connect-src 'self' https://o123456.ingest.us.sentry.io;
Don’t whitelist https://sentry.io unless that is actually the host your browser talks to. CSP should be as specific as possible.
Copy-paste CSP examples
1. Standard browser SDK setup
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
font-src 'self';
connect-src 'self' https://o0.ingest.sentry.io;
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
2. Sentry with Session Replay
Session Replay can require additional network calls, but in practice it still comes down to connect-src for the same Sentry ingestion host.
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob:;
font-src 'self';
connect-src 'self' https://o0.ingest.sentry.io;
worker-src 'self' blob:;
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
If replay or workers are involved, worker-src 'self' blob: may be needed depending on your app and build output. I’ve seen teams waste hours chasing a “Sentry issue” that was really a worker blocked by CSP.
3. Sentry using a tunnel on your own domain
A lot of teams do this to avoid ad blockers or to keep third-party endpoints out of the browser. In Sentry config:
Sentry.init({
dsn: "https://[email protected]/1234567",
tunnel: "/error-monitoring"
});
Then your CSP becomes much simpler:
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
connect-src 'self';
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
This is my preferred setup. Cleaner CSP, fewer issues with blockers, easier observability.
Your backend endpoint /error-monitoring forwards the event to Sentry server-side, so the browser only talks to your own origin.
4. If you load Sentry from a CDN
I don’t love this setup, but it exists. If you include Sentry from a third-party script URL, you need to allow that source in script-src too.
<script src="https://browser.sentry-cdn.com/8.31.0/bundle.min.js" crossorigin="anonymous"></script>
CSP:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://browser.sentry-cdn.com;
style-src 'self';
img-src 'self' data:;
connect-src 'self' https://o0.ingest.sentry.io;
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
If you use nonces or hashes, keep using them. Don’t weaken script-src just because a monitoring vendor wants convenience.
Sentry config examples
Bundled SDK
npm install @sentry/browser
import * as Sentry from "@sentry/browser";
Sentry.init({
dsn: "https://[email protected]/1234567",
tracesSampleRate: 0.1
});
CSP:
Content-Security-Policy:
default-src 'self';
script-src 'self';
connect-src 'self' https://o0.ingest.sentry.io;
object-src 'none';
base-uri 'self';
With a tunnel endpoint
import * as Sentry from "@sentry/browser";
Sentry.init({
dsn: "https://[email protected]/1234567",
tunnel: "/error-monitoring",
tracesSampleRate: 0.1
});
CSP:
Content-Security-Policy:
default-src 'self';
script-src 'self';
connect-src 'self';
object-src 'none';
base-uri 'self';
Express tunnel example
import express from "express";
import fetch from "node-fetch";
const app = express();
app.use(express.json({ limit: "1mb", type: "*/*" }));
app.post("/error-monitoring", async (req, res) => {
const sentryProjectId = "1234567";
const sentryHost = "o0.ingest.sentry.io";
const response = await fetch(`https://${sentryHost}/api/${sentryProjectId}/envelope/`, {
method: "POST",
headers: {
"Content-Type": req.headers["content-type"] || "application/x-sentry-envelope"
},
body: req.body
});
res.status(response.status).end();
});
app.listen(3000);
Production version should validate origin, rate-limit requests, and avoid becoming an open relay. Don’t paste this into prod without hardening it.
Common CSP mistakes with Sentry
1. Allowing the wrong directive
This is the classic one.
People add Sentry to:
script-src https://sentry.io
But the blocked request is actually an XHR or fetch. The fix is:
connect-src https://o0.ingest.sentry.io
2. Using default-src and assuming it’s enough
Technically, default-src is a fallback for connect-src if connect-src is absent. In real teams, explicit is better.
Bad:
default-src 'self' https://o0.ingest.sentry.io;
Better:
default-src 'self';
connect-src 'self' https://o0.ingest.sentry.io;
3. Whitelisting too broadly
This works:
connect-src 'self' https://*.sentry.io;
But it’s sloppier than necessary. Prefer the exact ingest host from your DSN.
4. Forgetting WebSocket endpoints
If some part of your tooling or observability setup uses WebSockets, you may need wss: entries in connect-src. A good real-world example is the CSP used by headertest.com, which includes both HTTPS and WebSocket observability endpoints:
connect-src 'self' https://api.headertest.com https://tallycdn.com https://or.headertest.com wss://or.headertest.com https://*.google-analytics.com https://*.googletagmanager.com https://*.cookiebot.com;
That’s not a Sentry config, but it’s a good reminder that connect-src covers more than fetch().
Testing your policy
If Sentry events don’t show up, open DevTools and look for messages like:
Refused to connect to 'https://o0.ingest.sentry.io/api/1234567/envelope/' because it violates the following Content Security Policy directive: "connect-src 'self'".
That message tells you exactly what to add.
You can also roll out changes in report-only mode first:
Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self';
connect-src 'self';
report-to csp-endpoint;
Or with the older reporting directive:
Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self';
connect-src 'self';
report-uri /csp-report;
If you want ready-made patterns for strict and framework-specific policies, csp-examples.com is useful for copy-paste starting points.
Framework examples
Nginx
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https://o0.ingest.sentry.io; object-src 'none'; base-uri 'self'; frame-ancestors 'none';" always;
Apache
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https://o0.ingest.sentry.io; object-src 'none'; base-uri 'self'; frame-ancestors 'none';"
Next.js headers
const csp = [
"default-src 'self'",
"script-src 'self'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data:",
"connect-src 'self' https://o0.ingest.sentry.io",
"object-src 'none'",
"base-uri 'self'",
"frame-ancestors 'none'"
].join("; ");
module.exports = {
async headers() {
return [
{
source: "/(.*)",
headers: [
{
key: "Content-Security-Policy",
value: csp
}
]
}
];
}
};
Good default policy for Sentry
If you want a practical starting point, use this and then tighten as needed:
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob:;
font-src 'self';
connect-src 'self' https://o0.ingest.sentry.io;
worker-src 'self' blob:;
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
form-action 'self';
Then adjust based on your actual setup:
- replace the ingest host with the one from your DSN
- remove
blob:if you don’t need it - remove
'unsafe-inline'if your styles are nonce/hash-based - switch to a tunnel if you want the cleanest browser CSP
If I had to give one opinionated recommendation: bundle Sentry locally and send events through a tunnel on your own origin. That avoids most CSP friction and tends to be the least annoying setup long term.