Back to Blog

How to Fix Content Security Policy (CSP) Errors: Common Problems and Solutions

Struggling with Content Security Policy errors blocking your external resources? Learn how to fix CSP violations, manage third-party content, and simplify your security configuration with practical solutions.

How to Fix Content Security Policy (CSP) Errors: Common Problems and Solutions

Content Security Policy (CSP) is one of the most powerful security features available to web developers, designed to prevent cross-site scripting (XSS) attacks and data injection attacks. However, implementing CSP correctly is notoriously challenging, and even experienced developers frequently encounter CSP errors that break functionality, block legitimate resources, and create frustrating debugging sessions.

In this comprehensive guide, we’ll explore the most common CSP problems developers face, understand why they occur, and learn practical solutions to fix them—including a powerful approach using CORS proxies to simplify CSP management.

Table of Contents

  1. Understanding Content Security Policy
  2. Common CSP Problems Developers Face
  3. The CORS Proxy Solution for CSP Management
  4. Implementing CSP with a CORS Proxy
  5. Best Practices for CSP Configuration
  6. Conclusion

Understanding Content Security Policy

Before diving into problems and solutions, let’s establish what CSP is and how it works.

Content Security Policy (CSP) is an HTTP response header that allows you to control which resources the browser is allowed to load for your web page. It’s a defense-in-depth security mechanism that significantly reduces the risk of XSS attacks by restricting:

  • Where scripts can be loaded from
  • Where styles can be loaded from
  • Where images, fonts, media, and other resources can originate
  • Whether inline scripts and styles are allowed
  • Which domains can be framed or can frame your content

Example CSP Header:

Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'

While CSP is incredibly powerful for security, its restrictive nature often creates friction during development and deployment.


Common CSP Problems Developers Face

1. Blocked External Resources (Scripts, Images, Fonts)

The Problem:

One of the most common CSP errors occurs when your application tries to load resources from external domains that aren’t whitelisted in your CSP policy.

Refused to load the script 'https://cdn.example.com/analytics.js' because it violates the following Content Security Policy directive: "script-src 'self'"

This happens when:

  • Using third-party CDNs for libraries (jQuery, React, etc.)
  • Loading images from external APIs or user-generated content platforms
  • Integrating web fonts from Google Fonts, Adobe Fonts, etc.
  • Fetching data from multiple external APIs

Traditional Solution:

Add each external domain to your CSP:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.example.com https://analytics.google.com;
  img-src 'self' https://images.unsplash.com https://user-uploads.s3.amazonaws.com;
  font-src 'self' https://fonts.gstatic.com;

The Problem with Traditional Solutions:

  • Your CSP header becomes massive and unmanageable
  • Every new external service requires a CSP update
  • You expose your security policy (listing all your dependencies)
  • Multiple external domains increase your attack surface

Better Solution: Use a CORS Proxy

Instead of whitelisting dozens of external domains, route all external resources through a CORS proxy:

// Before: Direct external resource loading (CSP violation)
const imageUrl = 'https://external-api.com/image.jpg';

// After: Proxied through corsproxy.io
const imageUrl = 'https://corsproxy.io/?url=https://external-api.com/image.jpg';

Now your CSP only needs to whitelist one domain:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://corsproxy.io;
  img-src 'self' https://corsproxy.io;
  connect-src 'self' https://corsproxy.io;

This approach centralizes external resource loading and dramatically simplifies CSP management.


2. Inline Scripts and Styles Violations

The Problem:

CSP blocks inline JavaScript and CSS by default to prevent XSS attacks:

<!-- This will be blocked -->
<script>
  console.log('Hello World');
</script>

<div style="color: red;">This is red</div>

Error Message:

Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'"

Why This Happens:

  • Legacy code with inline event handlers (onclick, onload, etc.)
  • Dynamic content from external sources containing inline scripts
  • Third-party widgets injecting inline code
  • Server-side rendered content with embedded scripts

Solutions:

Option 1: Use Nonces (Recommended)

<!-- Server generates unique nonce per request -->
<script nonce="random-nonce-value">
  console.log('Hello World');
</script>
Content-Security-Policy: script-src 'self' 'nonce-random-nonce-value'

Option 2: Use Hashes

Content-Security-Policy: script-src 'self' 'sha256-base64-hash-of-script'

Option 3: Load External Content Through Proxy

If you’re loading external HTML content that contains inline scripts, proxy it and process it server-side:

// Fetch and sanitize external content
const response = await fetch('https://corsproxy.io/?url=https://external-site.com/widget.html');
const html = await response.text();
// Extract scripts and load them properly with your CSP policy

3. Third-Party Service Integration Issues

The Problem:

Modern web applications rely on numerous third-party services:

  • Analytics (Google Analytics, Mixpanel, Amplitude)
  • Payment processors (Stripe, PayPal)
  • Chat widgets (Intercom, Drift)
  • Social media embeds (Twitter, Facebook, YouTube)
  • Advertising networks

Each service may load resources from multiple domains:

Google Analytics loads from:
- www.google-analytics.com
- www.googletagmanager.com
- stats.g.doubleclick.net

Traditional Approach Problem:

Content-Security-Policy:
  script-src 'self'
    https://www.google-analytics.com
    https://www.googletagmanager.com
    https://stats.g.doubleclick.net
    https://connect.facebook.net
    https://platform.twitter.com
    https://js.stripe.com
    https://cdn.segment.com
    /* ... and 20 more domains */

This becomes unmaintainable and creates security risks.

Proxy-Based Solution:

For resources you control or can proxy:

// Centralize third-party script loading
const loadScript = (externalUrl: string): void => {
  const script = document.createElement('script');
  script.src = `https://corsproxy.io/?url=${encodeURIComponent(externalUrl)}`;
  document.head.appendChild(script);
};

loadScript('https://cdn.example.com/analytics.js');

Your CSP remains clean:

Content-Security-Policy: script-src 'self' https://corsproxy.io

4. Dynamic Content Loading Problems

The Problem:

Applications that load user-generated content or data from multiple APIs face CSP challenges:

// Loading images from user uploads
users.forEach(user => {
  const img = document.createElement('img');
  img.src = user.profilePicture; // Could be any domain!
});

Error:

Refused to load the image 'https://random-user-cdn.com/photo.jpg' because it violates the following Content Security Policy directive: "img-src 'self'"

Solution with CORS Proxy:

const loadUserImage = (imageUrl: string): string => {
  // Proxy all external images through your CORS proxy
  if (imageUrl.startsWith('http') && !imageUrl.includes(window.location.hostname)) {
    return `https://corsproxy.io/?url=${encodeURIComponent(imageUrl)}`;
  }
  return imageUrl;
};

users.forEach(user => {
  const img = document.createElement('img');
  img.src = loadUserImage(user.profilePicture);
});

CSP Configuration:

Content-Security-Policy: img-src 'self' https://corsproxy.io data:

This allows you to load any external image while maintaining a strict CSP policy.


5. CSP Whitelist Management Nightmare

The Problem:

As your application grows, managing CSP whitelists becomes increasingly complex:

  • Different environments need different policies (dev vs staging vs production)
  • Each feature addition requires CSP updates
  • Removing unused services means auditing the entire CSP
  • Coordinating CSP changes across teams is difficult
  • CSP headers can exceed browser limits (4KB - 8KB depending on browser)

Example of CSP Sprawl:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'unsafe-inline' 'unsafe-eval'
    https://cdn.jsdelivr.net
    https://unpkg.com
    https://cdnjs.cloudflare.com
    https://www.google-analytics.com
    https://www.googletagmanager.com
    https://connect.facebook.net
    https://platform.twitter.com
    https://js.stripe.com
    https://cdn.segment.com
    https://static.cloudflareinsights.com
    https://challenges.cloudflare.com;
  img-src 'self' data: https:
    https://www.google-analytics.com
    https://images.unsplash.com
    https://res.cloudinary.com
    https://*.amazonaws.com;
  connect-src 'self'
    https://api.github.com
    https://api.stripe.com
    https://analytics.google.com
    https://*.sentry.io;

Proxy-Centralized Solution:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://corsproxy.io;
  img-src 'self' https://corsproxy.io data:;
  connect-src 'self' https://corsproxy.io;
  style-src 'self' https://corsproxy.io;
  font-src 'self' https://corsproxy.io;

Benefits:

  • Single source of truth for external resources
  • Easy to audit and maintain
  • Works across all environments
  • No CSP size limitations
  • Clear security boundary

6. eval() and Function Constructor Blocks

The Problem:

CSP blocks eval() and the Function constructor by default, breaking libraries that use them:

// This will be blocked
eval('console.log("test")');
new Function('return 1 + 1')();

Affected Libraries:

  • Template engines (Handlebars, Lodash templates)
  • Math libraries (math.js)
  • Some bundlers’ development modes
  • Legacy code using eval()

Error:

Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script

Solutions:

Option 1: Avoid Libraries Using eval() (Recommended)

Replace libraries with modern alternatives that don’t use eval().

Option 2: Add ‘unsafe-eval’ (Not Recommended)

Content-Security-Policy: script-src 'self' 'unsafe-eval'

⚠️ This significantly weakens your security posture.

Option 3: Proxy and Transform Code

If you must use such libraries from external sources:

// Fetch library through proxy, transform it server-side to remove eval()
const response = await fetch('https://corsproxy.io/?url=https://legacy-lib.com/library.js&transform=no-eval');

7. Framework-Generated Code Violations

The Problem:

Modern frameworks (React, Vue, Angular) sometimes generate code that violates CSP:

  • React’s development warnings use inline styles
  • Vue’s template compilation can use new Function()
  • Angular’s JIT compiler uses eval()
  • CSS-in-JS libraries inject inline styles

Example Error with React:

Refused to apply inline style because it violates the following Content Security Policy directive: "style-src 'self'"

Solutions:

For React:

Use production builds and avoid inline styles:

// Instead of inline styles
<div style={{ color: 'red' }}>Text</div>

// Use CSS modules or styled-components with nonces
<div className={styles.redText}>Text</div>

For Vue:

Use pre-compiled templates and avoid runtime template compilation:

// vue.config.js
module.exports = {
  runtimeCompiler: false // Disable runtime compilation
}

For CSS-in-JS Libraries:

Many support CSP with nonces:

// styled-components with CSP
import { ServerStyleSheet } from 'styled-components';

const sheet = new ServerStyleSheet();
const html = renderToString(sheet.collectStyles(<App />));
const styleTags = sheet.getStyleTags(); // Includes nonce

8. Form Submission and Navigation Blocks

The Problem:

CSP’s form-action directive can block form submissions to external domains:

<!-- This form submission might be blocked -->
<form action="https://external-payment-processor.com/pay" method="POST">
  <button type="submit">Pay Now</button>
</form>

Error:

Refused to send form data to 'https://external-payment-processor.com/pay' because it violates the following Content Security Policy directive: "form-action 'self'"

Solution with Proxy:

const handleFormSubmit = async (e: Event): Promise<void> => {
  e.preventDefault();
  const form = e.target as HTMLFormElement;
  const formData = new FormData(form);

  // Submit through proxy
  const response = await fetch('https://corsproxy.io/?url=https://external-payment-processor.com/pay', {
    method: 'POST',
    body: formData
  });

  const result = await response.json();
  // Handle response
};

CSP:

Content-Security-Policy: form-action 'self'; connect-src 'self' https://corsproxy.io

The CORS Proxy Solution for CSP Management

Using a CORS proxy like corsproxy.io offers a comprehensive solution to many CSP challenges:

How It Works

  1. Centralized External Resource Loading: All external resources are fetched through a single proxy domain
  2. Simplified CSP Configuration: Your CSP only needs to whitelist the proxy domain
  3. Consistent Security Boundary: One domain to audit and secure
  4. Environment Agnostic: Same CSP works across dev, staging, and production
  5. Dynamic Resource Support: No CSP updates needed when adding new external services

Architecture Example

// utils/resourceLoader.ts
const PROXY_URL = 'https://corsproxy.io/?url=';

export const proxyUrl = (url: string): string => {
  // Don't proxy same-origin resources
  if (url.startsWith('/') || url.includes(window.location.hostname)) {
    return url;
  }

  // Proxy external resources
  return `${PROXY_URL}${encodeURIComponent(url)}`;
};

export const loadImage = (src: string): Promise<HTMLImageElement> => {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => resolve(img);
    img.onerror = reject;
    img.src = proxyUrl(src);
  });
};

export const loadScript = (src: string): Promise<void> => {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.onload = () => resolve();
    script.onerror = reject;
    script.src = proxyUrl(src);
    document.head.appendChild(script);
  });
};

export const fetchData = async (url: string, options?: RequestInit): Promise<Response> => {
  return fetch(proxyUrl(url), options);
};

Usage in Your Application

// Load external images
await loadImage('https://external-cdn.com/image.jpg');

// Load third-party scripts
await loadScript('https://analytics-provider.com/analytics.js');

// Fetch API data
const data = await fetchData('https://api.example.com/users');

Simplified CSP Header

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://corsproxy.io 'nonce-{random}';
  img-src 'self' https://corsproxy.io data:;
  style-src 'self' https://corsproxy.io;
  font-src 'self' https://corsproxy.io;
  connect-src 'self' https://corsproxy.io;
  frame-src 'self' https://corsproxy.io;

Implementing CSP with a CORS Proxy

Step 1: Set Up Your CSP Header

For Express.js:

const helmet = require('helmet');

app.use(
  helmet.contentSecurityPolicy({
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "https://corsproxy.io"],
      imgSrc: ["'self'", "https://corsproxy.io", "data:"],
      styleSrc: ["'self'", "https://corsproxy.io"],
      fontSrc: ["'self'", "https://corsproxy.io"],
      connectSrc: ["'self'", "https://corsproxy.io"],
      frameSrc: ["'self'", "https://corsproxy.io"],
    },
  })
);

For Next.js:

// next.config.js
const ContentSecurityPolicy = `
  default-src 'self';
  script-src 'self' https://corsproxy.io;
  img-src 'self' https://corsproxy.io data:;
  style-src 'self' https://corsproxy.io;
  font-src 'self' https://corsproxy.io;
  connect-src 'self' https://corsproxy.io;
  frame-src 'self' https://corsproxy.io;
`;

const securityHeaders = [
  {
    key: 'Content-Security-Policy',
    value: ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim()
  }
];

module.exports = {
  async headers() {
    return [
      {
        source: '/:path*',
        headers: securityHeaders,
      },
    ];
  },
};

Step 2: Create Resource Loading Utilities

// lib/csp-safe-loader.ts

const CORSPROXY_URL = 'https://corsproxy.io/?url=';

interface LoadOptions {
  bypassProxy?: boolean;
}

export class CSPSafeLoader {
  private static proxyUrl(url: string, bypass: boolean = false): string {
    if (bypass || this.isSameOrigin(url)) {
      return url;
    }
    return `${CORSPROXY_URL}${encodeURIComponent(url)}`;
  }

  private static isSameOrigin(url: string): boolean {
    if (url.startsWith('/')) return true;
    try {
      const urlObj = new URL(url);
      return urlObj.origin === window.location.origin;
    } catch {
      return false;
    }
  }

  static async loadImage(
    src: string,
    options: LoadOptions = {}
  ): Promise<HTMLImageElement> {
    const img = new Image();
    img.crossOrigin = 'anonymous';

    return new Promise((resolve, reject) => {
      img.onload = () => resolve(img);
      img.onerror = () => reject(new Error(`Failed to load image: ${src}`));
      img.src = this.proxyUrl(src, options.bypassProxy);
    });
  }

  static async loadScript(
    src: string,
    options: LoadOptions = {}
  ): Promise<void> {
    const script = document.createElement('script');
    script.crossOrigin = 'anonymous';

    return new Promise((resolve, reject) => {
      script.onload = () => resolve();
      script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
      script.src = this.proxyUrl(src, options.bypassProxy);
      document.head.appendChild(script);
    });
  }

  static async loadStylesheet(
    href: string,
    options: LoadOptions = {}
  ): Promise<void> {
    const link = document.createElement('link');
    link.rel = 'stylesheet';
    link.crossOrigin = 'anonymous';

    return new Promise((resolve, reject) => {
      link.onload = () => resolve();
      link.onerror = () => reject(new Error(`Failed to load stylesheet: ${href}`));
      link.href = this.proxyUrl(href, options.bypassProxy);
      document.head.appendChild(link);
    });
  }

  static async fetchJSON<T = any>(
    url: string,
    options: RequestInit & LoadOptions = {}
  ): Promise<T> {
    const { bypassProxy, ...fetchOptions } = options;
    const response = await fetch(this.proxyUrl(url, bypassProxy), fetchOptions);

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    return response.json();
  }

  static getProxiedUrl(url: string): string {
    return this.proxyUrl(url);
  }
}

Step 3: Use in Your Application

// components/ExternalImage.tsx
import { CSPSafeLoader } from '@/lib/csp-safe-loader';
import { useEffect, useState } from 'react';

interface ExternalImageProps {
  src: string;
  alt: string;
}

export const ExternalImage: React.FC<ExternalImageProps> = ({ src, alt }) => {
  const [imageSrc, setImageSrc] = useState<string>('');
  const [error, setError] = useState<boolean>(false);

  useEffect(() => {
    CSPSafeLoader.loadImage(src)
      .then(img => setImageSrc(img.src))
      .catch(() => setError(true));
  }, [src]);

  if (error) return <div>Failed to load image</div>;
  if (!imageSrc) return <div>Loading...</div>;

  return <img src={imageSrc} alt={alt} />;
};
// pages/api/external-data.ts
import { CSPSafeLoader } from '@/lib/csp-safe-loader';
import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  try {
    const data = await CSPSafeLoader.fetchJSON('https://api.example.com/data');
    res.status(200).json(data);
  } catch (error) {
    res.status(500).json({ error: 'Failed to fetch data' });
  }
}

Step 4: Monitor CSP Violations

Content-Security-Policy-Report-Only:
  default-src 'self';
  script-src 'self' https://corsproxy.io;
  report-uri /api/csp-report;
// pages/api/csp-report.ts
import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method === 'POST') {
    const violation = req.body;

    // Log CSP violations
    console.error('CSP Violation:', {
      documentUri: violation['document-uri'],
      violatedDirective: violation['violated-directive'],
      blockedUri: violation['blocked-uri'],
      timestamp: new Date().toISOString(),
    });

    // Send to monitoring service (e.g., Sentry)
    // await sendToMonitoring(violation);

    res.status(204).end();
  } else {
    res.status(405).end();
  }
}

Best Practices for CSP Configuration

1. Start with Report-Only Mode

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /api/csp-report

Monitor violations before enforcing the policy.

2. Use Strict Policies in Production

Content-Security-Policy:
  default-src 'none';
  script-src 'self' https://corsproxy.io;
  img-src 'self' https://corsproxy.io data:;
  style-src 'self' https://corsproxy.io;
  font-src 'self' https://corsproxy.io;
  connect-src 'self' https://corsproxy.io;
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';
  upgrade-insecure-requests;

3. Avoid ‘unsafe-inline’ and ‘unsafe-eval’

These directives defeat much of CSP’s purpose. Use nonces or hashes instead.

4. Use Subresource Integrity (SRI)

<script
  src="https://corsproxy.io/?url=https://cdn.example.com/library.js"
  integrity="sha384-hash-value"
  crossorigin="anonymous"
></script>

5. Implement Defense in Depth

CSP is one layer of security. Combine it with:

  • HTTPS everywhere
  • Secure cookies (HttpOnly, Secure, SameSite)
  • Input validation and output encoding
  • Regular security audits

6. Keep CSP Updated

Regularly review and update your CSP as your application evolves.

7. Test Across Browsers

Different browsers have varying CSP support. Test your policy in:

  • Chrome/Edge (Chromium)
  • Firefox
  • Safari
  • Mobile browsers

Conclusion

Content Security Policy is a critical security feature, but implementing it correctly requires understanding common pitfalls and adopting smart strategies for managing external resources.

Key Takeaways:

  1. CSP errors are common when loading external resources, using inline scripts, or integrating third-party services
  2. Traditional CSP management becomes unwieldy as applications grow
  3. CORS proxies like corsproxy.io offer a powerful solution by centralizing external resource loading
  4. Simplified CSP configuration improves security, maintainability, and developer experience
  5. Proper implementation requires planning, testing, and monitoring

By using a CORS proxy to centralize external resources, you can:

Maintain a strict, manageable CSP policy Reduce your attack surface to a single trusted domain Simplify CSP updates and maintenance Support dynamic external content loading Work consistently across all environments

Whether you’re building a new application or retrofitting CSP into an existing one, the proxy-based approach offers a practical path forward that balances security with functionality.

Ready to simplify your CSP management? Start using corsproxy.io to centralize your external resource loading and maintain a strict Content Security Policy without the configuration nightmare.


Have questions about CSP or need help implementing these solutions? Feel free to reach out or explore our other guides on web security and cross-origin resource management.

Related blog posts

Create a free Account to fix CORS Errors in Production

Say goodbye to CORS errors and get back to building great web applications. It's free!

CORSPROXY Dashboard