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
- Understanding Content Security Policy
- Common CSP Problems Developers Face
- 1. Blocked External Resources (Scripts, Images, Fonts)
- 2. Inline Scripts and Styles Violations
- 3. Third-Party Service Integration Issues
- 4. Dynamic Content Loading Problems
- 5. CSP Whitelist Management Nightmare
- 6. eval() and Function Constructor Blocks
- 7. Framework-Generated Code Violations
- 8. Form Submission and Navigation Blocks
- The CORS Proxy Solution for CSP Management
- Implementing CSP with a CORS Proxy
- Best Practices for CSP Configuration
- 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
- Centralized External Resource Loading: All external resources are fetched through a single proxy domain
- Simplified CSP Configuration: Your CSP only needs to whitelist the proxy domain
- Consistent Security Boundary: One domain to audit and secure
- Environment Agnostic: Same CSP works across dev, staging, and production
- 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:
- CSP errors are common when loading external resources, using inline scripts, or integrating third-party services
- Traditional CSP management becomes unwieldy as applications grow
- CORS proxies like corsproxy.io offer a powerful solution by centralizing external resource loading
- Simplified CSP configuration improves security, maintainability, and developer experience
- 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.