Fix SecurityError: Refused to execute inline script because it violates the following Content Security Policy directive in Express
This error occurs when Helmet's Content Security Policy blocks inline scripts, styles, or external resources. The CSP header tells the browser to reject anything not explicitly whitelisted. Fix it by configuring Helmet's CSP directives to allow your legitimate sources using nonces, hashes, or specific domain allowlists.
Reading the Stack Trace
Here's what each line means:
- Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'".: The browser blocked an inline script because the CSP header only allows scripts from the same origin ('self'), not inline scripts.
- at helmet (/app/node_modules/helmet/dist/index.js:8:12): Helmet middleware set the Content-Security-Policy header with default restrictive directives that block inline scripts.
- at Object.use (/app/src/app.js:12:5): Your app.js at line 12 registers Helmet with default settings, which enable a strict CSP that blocks inline resources.
Common Causes
1. Default Helmet CSP blocks inline scripts
Using Helmet with default settings sets a strict CSP that disallows all inline scripts and styles, breaking pages that rely on them.
const helmet = require('helmet');
const app = express();
app.use(helmet()); // Default CSP blocks inline scripts
app.get('/', (req, res) => {
res.send('<html><body><script>alert("hello")</script></body></html>');
});
2. Third-party scripts not whitelisted
External scripts from CDNs or analytics providers are blocked because they are not included in the script-src directive.
app.use(helmet({
contentSecurityPolicy: {
directives: {
scriptSrc: ["'self'"]
// Missing CDN and analytics domains
}
}
}));
3. Missing nonce for server-rendered inline scripts
Server-rendered pages include inline scripts but the CSP does not include a nonce to authorize them.
app.get('/', (req, res) => {
res.send(`<html><body><script>window.__DATA__=${JSON.stringify(data)}</script></body></html>`);
});
The Fix
Generate a unique nonce per request and include it in both the CSP header and the inline script tag. This allows specific inline scripts while maintaining CSP protection against injection attacks.
const helmet = require('helmet');
const app = express();
app.use(helmet());
app.get('/', (req, res) => {
res.send('<html><body><script>alert("hello")</script></body></html>');
});
const crypto = require('crypto');
const helmet = require('helmet');
const app = express();
app.use((req, res, next) => {
res.locals.cspNonce = crypto.randomBytes(16).toString('base64');
next();
});
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", (req, res) => `'nonce-${res.locals.cspNonce}'`],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:"],
connectSrc: ["'self'"]
}
}
}));
app.get('/', (req, res) => {
const nonce = res.locals.cspNonce;
res.send(`<html><body><script nonce="${nonce}">alert("hello")</script></body></html>`);
});
Testing the Fix
const request = require('supertest');
const express = require('express');
const crypto = require('crypto');
const helmet = require('helmet');
function createApp() {
const app = express();
app.use((req, res, next) => {
res.locals.cspNonce = crypto.randomBytes(16).toString('base64');
next();
});
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", (req, res) => `'nonce-${res.locals.cspNonce}'`]
}
}
}));
app.get('/', (req, res) => {
res.send(`<script nonce="${res.locals.cspNonce}">console.log('ok')</script>`);
});
return app;
}
describe('CSP with nonce', () => {
it('includes CSP header with nonce', async () => {
const res = await request(createApp()).get('/');
expect(res.status).toBe(200);
expect(res.headers['content-security-policy']).toContain('nonce-');
});
it('nonce in header matches nonce in script tag', async () => {
const res = await request(createApp()).get('/');
const csp = res.headers['content-security-policy'];
const nonceMatch = csp.match(/nonce-([A-Za-z0-9+/=]+)/);
expect(nonceMatch).not.toBeNull();
expect(res.text).toContain(`nonce="${nonceMatch[1]}"`);
});
});
Run your tests:
npx jest --testPathPattern=helmet
Pushing Through CI/CD
git checkout -b fix/express-helmet-csp-error,git add src/middleware/csp.js src/__tests__/helmet.test.js,git commit -m "fix: configure CSP nonce for inline script support",git push origin fix/express-helmet-csp-error
Your CI config should look something like this:
name: CI
on:
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npx jest --coverage
- run: npm run lint
The Full Manual Process: 18 Steps
Here's every step you just went through to fix this one bug:
- Notice the error alert or see it in your monitoring tool
- Open the error dashboard and read the stack trace
- Identify the file and line number from the stack trace
- Open your IDE and navigate to the file
- Read the surrounding code to understand context
- Reproduce the error locally
- Identify the root cause
- Write the fix
- Run the test suite locally
- Fix any failing tests
- Write new tests covering the edge case
- Run the full test suite again
- Create a new git branch
- Commit and push your changes
- Open a pull request
- Wait for code review
- Merge and deploy to production
- Monitor production to confirm the error is resolved
Total time: 30-60 minutes. For one bug.
Or Let bugstack Fix It in Under 2 minutes
Every step above? bugstack does it automatically.
Step 1: Install the SDK
npm install bugstack-sdk
Step 2: Initialize
const { initBugStack } = require('bugstack-sdk')
initBugStack({ apiKey: process.env.BUGSTACK_API_KEY })
Step 3: There is no step 3.
bugstack handles everything from here:
- Captures the stack trace and request context
- Pulls the relevant source files from your GitHub repo
- Analyzes the error and understands the code context
- Generates a minimal, verified fix
- Runs your existing test suite
- Pushes through your CI/CD pipeline
- Deploys to production (or opens a PR for review)
Time from error to fix deployed: Under 2 minutes.
Human involvement: zero.
Try bugstack Free →No credit card. 5-minute setup. Cancel anytime.
Deploying the Fix (Manual Path)
- Run the test suite locally to confirm CSP headers include the correct nonce.
- Open a pull request with the CSP configuration changes.
- Wait for CI checks to pass on the PR.
- Have a teammate review and approve the PR.
- Merge to main and verify no scripts are blocked in staging before promoting to production.
Frequently Asked Questions
BugStack verifies that CSP headers contain the correct nonce, checks that inline scripts execute without violations, and confirms no new security holes are introduced before marking it safe.
Every fix is delivered as a pull request with full CI validation. Your team reviews and approves before anything reaches production.
No. Using unsafe-inline disables CSP protection against cross-site scripting attacks. Always prefer nonces or hashes, which allow specific inline scripts while blocking injected ones.
Yes. Reusing nonces defeats the purpose of CSP. Each request must generate a unique cryptographically random nonce so attackers cannot predict or reuse it.