How It Works Features Pricing Blog Error Guides
Log In Start Free Trial
Express · JavaScript

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

Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-abc123...'), or a nonce ('nonce-xyz789...') is required to enable inline execution. // Browser console error — no server-side stack trace. // The CSP header was set by Helmet middleware: at Object.use (/app/src/app.js:12:5) at helmet (/app/node_modules/helmet/dist/index.js:8:12) at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5) at trim_prefix (/app/node_modules/express/lib/router/index.js:328:13) at /app/node_modules/express/lib/router/index.js:284:15 at Function.process_params (/app/node_modules/express/lib/router/index.js:346:12) at next (/app/node_modules/express/lib/router/index.js:280:10)

Here's what each line means:

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.

Before (broken)
const helmet = require('helmet');
const app = express();

app.use(helmet());

app.get('/', (req, res) => {
  res.send('<html><body><script>alert("hello")</script></body></html>');
});
After (fixed)
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:

  1. Notice the error alert or see it in your monitoring tool
  2. Open the error dashboard and read the stack trace
  3. Identify the file and line number from the stack trace
  4. Open your IDE and navigate to the file
  5. Read the surrounding code to understand context
  6. Reproduce the error locally
  7. Identify the root cause
  8. Write the fix
  9. Run the test suite locally
  10. Fix any failing tests
  11. Write new tests covering the edge case
  12. Run the full test suite again
  13. Create a new git branch
  14. Commit and push your changes
  15. Open a pull request
  16. Wait for code review
  17. Merge and deploy to production
  18. 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:

  1. Captures the stack trace and request context
  2. Pulls the relevant source files from your GitHub repo
  3. Analyzes the error and understands the code context
  4. Generates a minimal, verified fix
  5. Runs your existing test suite
  6. Pushes through your CI/CD pipeline
  7. 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)

  1. Run the test suite locally to confirm CSP headers include the correct nonce.
  2. Open a pull request with the CSP configuration changes.
  3. Wait for CI checks to pass on the PR.
  4. Have a teammate review and approve the PR.
  5. 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.