You are viewing a preview of this lesson. Sign in to start learning
Back to Web Security: The Modern Browser Model

Cross-Origin Resource Policy

Use CORP headers to explicitly declare which origins can load your resources

Introduction to Cross-Origin Resource Policy (CORP)

Imagine you're building a web application that displays sensitive user photos. You've carefully configured CORS (Cross-Origin Resource Sharing) to control which websites can request your images via JavaScript. Everything seems secureβ€”until you learn that a malicious website can still embed your images in <img> tags, measure how long they take to load, and potentially extract sensitive information through timing attacks. This is exactly the vulnerability that Cross-Origin Resource Policy was designed to prevent. In this lesson, we'll explore how CORP closes security gaps that CORS never addressed, and you can reinforce your learning with free flashcards embedded throughout.

What is Cross-Origin Resource Policy?

Cross-Origin Resource Policy (CORP) is an HTTP response header that allows servers to declare whether their resources can be loaded by other origins, regardless of how those resources are requested. Unlike CORS, which governs programmatic access through JavaScript APIs like fetch() or XMLHttpRequest, CORP controls whether browsers will even load a resource when embedded via HTML tags like <img>, <script>, <video>, or <iframe>.

🎯 Key Principle: CORP is a resource-level security policy that operates at the network layer, before the browser fully processes the response. It's your first line of defense against cross-origin resource inclusion attacks.

Why CORP Was Introduced: The Spectre Connection

In 2018, the Spectre and Meltdown vulnerabilities revealed that attackers could exploit CPU speculative execution to read arbitrary memory. In the browser context, this meant a malicious website could potentially read cross-origin data that had been loaded into the browser's memoryβ€”even if JavaScript APIs blocked access to that data.

πŸ€” Did you know? Spectre-style attacks work by measuring tiny timing differences (microseconds) in how the CPU processes data. If an attacker can get your browser to load a sensitive resource, they might extract information about its contents without ever "seeing" it directly.

CORP acts as a defense-in-depth mechanism. By preventing cross-origin resources from loading in the first place, it ensures they never enter the attacker's process memory space:

Cross-Origin-Resource-Policy: same-origin

This simple header tells the browser: "Don't load this resource unless the request comes from the exact same origin."

CORP vs. CORS: Understanding the Difference

Many developers confuse CORP with CORS because both deal with cross-origin interactions. Here's the critical distinction:

❌ Wrong thinking: "CORS already protects my API, so I don't need CORP"
βœ… Correct thinking: "CORS protects programmatic access; CORP protects resource embedding"

πŸ“‹ Quick Reference Card:

Feature πŸ”’ CORS πŸ›‘οΈ CORP
🎯 Purpose Controls JavaScript API access Controls resource loading/embedding
πŸ”§ Affects fetch(), XMLHttpRequest <img>, <script>, <link>, etc.
πŸ“ Header Access-Control-Allow-Origin Cross-Origin-Resource-Policy
🚨 Threat Model Unauthorized data reading Side-channel attacks, resource leakage

πŸ’‘ Real-World Example: A private image at https://bank.com/account-balance.png might have CORS headers that block JavaScript from reading it. However, without CORP, an attacker at https://evil.com could still embed it using <img src="https://bank.com/account-balance.png">. While they can't read the pixels, they could measure load times, detect whether it exists, or exploit other side-channel information.

The Three CORP Directive Values

CORP provides three simple but powerful values:

1. same-origin - The most restrictive option:

Cross-Origin-Resource-Policy: same-origin

πŸ”’ Only requests from the exact same origin (protocol + domain + port) can load this resource. Use this for sensitive internal resources like admin dashboards or private user data.

2. same-site - Allows related domains:

Cross-Origin-Resource-Policy: same-site

🏒 Permits loading from the same site (e.g., both app.example.com and cdn.example.com can access resources). Perfect for multi-subdomain architectures where you control all the domains.

3. cross-origin - Explicitly opts into cross-origin loading:

Cross-Origin-Resource-Policy: cross-origin

🌐 Allows any origin to load the resource. Use this for truly public assets like open-source libraries, public images, or CDN-hosted content meant for widespread use.

πŸ’‘ Mental Model: Think of CORP values as concentric circles of trust: same-origin is your inner circle, same-site is your extended family, and cross-origin is the general public.

⚠️ Common Mistake: Mistake 1: Setting same-origin on CDN resources that need to be loaded by your main application domain. This breaks legitimate cross-origin loading! ⚠️

🧠 Mnemonic: Remember the order from strictest to most permissive: S-S-C (Same-origin β†’ Same-site β†’ Cross-origin) - "Super Secure, then Collaborative!"

By understanding these three directives and when to apply them, you're already ahead of most web developers in implementing modern browser security. In the next section, we'll dive into the practical implementation details and see how browsers actually enforce these policies.

Implementing CORP Headers and Policies

Now that you understand why CORP exists, let's dive into the practical implementation. The Cross-Origin-Resource-Policy header is remarkably simple in syntax but powerful in effect. It controls whether a resource can be loaded by cross-origin or cross-site requests, giving you fine-grained control over your server's resources.

Syntax and Configuration

The CORP header accepts exactly three values, each defining a different security boundary. Let's examine each with concrete server configuration examples:

## Apache: Prevent any cross-origin loading
Header set Cross-Origin-Resource-Policy "same-origin"

## Apache: Allow same-site but not cross-site loading
Header set Cross-Origin-Resource-Policy "same-site"

## Apache: Allow unrestricted cross-origin loading
Header set Cross-Origin-Resource-Policy "cross-origin"
## Nginx: Configuring CORP for different resource types
location /api/ {
    add_header Cross-Origin-Resource-Policy "same-origin";
}

location /images/ {
    add_header Cross-Origin-Resource-Policy "cross-origin";
}

location /user-uploads/ {
    add_header Cross-Origin-Resource-Policy "same-site";
}

🎯 Key Principle: The stricter the policy, the more protected your resources are from Spectre-like timing attacks, but the less flexible your resource sharing becomes.

Understanding Same-Origin vs Same-Site

The distinction between same-origin and same-site is critical for correct implementation. Let's visualize this with a practical domain scenario:

Origin A: https://app.example.com:443
Origin B: https://api.example.com:443
Origin C: https://example.com:443
Origin D: https://evil.com:443

Same-Origin Check (from Origin A):
βœ… https://app.example.com:443/page  β†’ PASS (exact match)
❌ https://api.example.com:443/data  β†’ FAIL (different subdomain)
❌ http://app.example.com:80/page    β†’ FAIL (different protocol/port)

Same-Site Check (from Origin A):
βœ… https://app.example.com:443/page  β†’ PASS
βœ… https://api.example.com:443/data  β†’ PASS (same registrable domain)
βœ… https://example.com:443/home      β†’ PASS
❌ https://evil.com:443/attack       β†’ FAIL (different site)

πŸ’‘ Mental Model: Think of same-origin as "exact address match" and same-site as "same neighborhood" (registrable domain).

Browser Enforcement by Resource Type

Browsers enforce CORP differently depending on the resource being loaded. Here's how the enforcement works:

// JavaScript example showing CORP enforcement scenarios

// Image from cross-origin with CORP: same-origin β†’ BLOCKED
const img = new Image();
img.src = 'https://api.example.com/profile.jpg'; // Has CORP: same-origin
img.onerror = () => console.log('CORP blocked this image');

// Script from same-site with CORP: same-site β†’ ALLOWED
const script = document.createElement('script');
script.src = 'https://cdn.example.com/app.js'; // Has CORP: same-site

// Fetch API respects CORP for no-cors mode
fetch('https://other.com/data.json', {mode: 'no-cors'})
  .then(response => {
    // If CORP blocks, this fails silently
    console.log(response.type); // 'opaque' if CORP allows
  });

Resource-specific enforcement:

  • πŸ–ΌοΈ Images: CORP blocks rendering; onerror event fires
  • πŸ“œ Scripts: Execution prevented; script tag fails to load
  • 🎨 Stylesheets: CSS not applied; page renders unstyled
  • πŸͺŸ Iframes: Navigation blocked; iframe remains empty

⚠️ Common Mistake: Assuming CORS and CORP are the same. CORS controls whether JavaScript can read responses. CORP controls whether the browser will load the resource at all, even into process memory. ⚠️

Testing with Browser DevTools

When CORP blocks a resource, modern browsers provide clear indicators in the DevTools Console:

❌ Cross-Origin-Resource-Policy: same-origin blocked cross-origin request
   to https://cdn.other.com/image.png

[Network Tab]
Status: (blocked:NotSameOrigin)
Type: png
Initiator: <img> element

πŸ’‘ Pro Tip: Open DevTools Network tab, filter by "blocked:" to quickly identify all CORP-blocked resources. The Console will show the exact policy that caused the block.

Integration Patterns

For real-world deployments, consider these patterns:

CDN Pattern: Set Cross-Origin-Resource-Policy: cross-origin for public assets you want embeddable anywhere (fonts, public images, libraries).

API Pattern: Use Cross-Origin-Resource-Policy: same-origin for sensitive API endpoints, forcing all access through your own frontend.

User Content Pattern: Apply Cross-Origin-Resource-Policy: same-site for user-uploaded content, allowing your subdomains to access it but preventing external embedding.

πŸ€” Did you know? Some CDNs now support setting CORP headers via query parameters or dashboard settings, making deployment easier without touching origin server configs.

Common Pitfalls and Best Practices

Implementing CORP effectively requires understanding where deployments typically go wrong and how to avoid breaking legitimate functionality while maintaining security.

The Danger of Overly Restrictive Policies

⚠️ Common Mistake 1: Setting same-origin on all public assets ⚠️

The most frequent error is applying overly restrictive policies to resources that legitimately need cross-origin access. Setting Cross-Origin-Resource-Policy: same-origin on CDN-hosted images, fonts, or scripts will break embedding on partner sites or external applications.

## ❌ Wrong: Breaks legitimate embeds
Cross-Origin-Resource-Policy: same-origin
Content-Type: image/png
## This image can't be used by your blog subdomain or partners
## βœ… Correct: Allow cross-origin for public assets
Cross-Origin-Resource-Policy: cross-origin
Content-Type: image/png
## This image can be embedded anywhere

πŸ’‘ Pro Tip: Start with cross-origin for public assets and only restrict to same-site or same-origin for sensitive resources like user avatars, private documents, or authenticated API responses.

Debugging Blocked Resources

When CORP blocks a resource, browsers display specific console errors that help identify the issue:

CORSS policy: The resource at 'https://api.example.com/data.json' 
was blocked by Cross-Origin-Resource-Policy header with value 'same-origin'.

πŸ”§ Debugging checklist:

  • Check the resource's response headers in DevTools Network tab
  • Verify the requesting page's origin matches policy expectations
  • Confirm same-site policies align with your eTLD+1 boundaries
  • Test with CORP temporarily removed to confirm it's the blocker

Balancing Security with Functionality

🎯 Key Principle: Classify your resources before applying policies. Not all content needs the same protection level.

Resource Type Recommended CORP Rationale
🌐 Public images/fonts cross-origin Designed for embedding
πŸ”’ User avatars same-origin Privacy-sensitive
πŸ“Š Analytics endpoints same-site Internal infrastructure
🎨 Public CSS/JS libraries cross-origin Shared resources
πŸ” API with user data same-origin High sensitivity

Migration Strategies

Adding CORP to existing applications requires careful rollout to avoid breaking production:

// Phase 1: Report-only monitoring (custom logging)
app.use((req, res, next) => {
  const policy = determinePolicy(req.path);
  res.set('Cross-Origin-Resource-Policy-Report-Only', policy);
  // Log violations without blocking
  next();
});

// Phase 2: Gradual enforcement after monitoring
app.use((req, res, next) => {
  if (req.path.startsWith('/api/private')) {
    res.set('Cross-Origin-Resource-Policy', 'same-origin');
  } else if (req.path.startsWith('/cdn')) {
    res.set('Cross-Origin-Resource-Policy', 'cross-origin');
  }
  next();
});

πŸ’‘ Real-World Example: A media company rolled out CORP by first applying it only to new content uploads, monitoring for 2 weeks, then gradually expanding to legacy content with override capabilities for known partners.

Using CORP with COEP and COOP

CORP works alongside Cross-Origin-Embedder-Policy (COEP) and Cross-Origin-Opener-Policy (COOP) for comprehensive isolation:

Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin

⚠️ When COEP is enabled, it requires all embedded resources to explicitly opt-in via CORP or CORS. This creates a strict isolation boundary enabling features like SharedArrayBuffer.

Summary

You now understand how to avoid common CORP implementation mistakes and deploy policies that balance security with functionality. The key insight is that resource classification drives policy selectionβ€”public assets need permissive policies while sensitive data requires restriction.

⚠️ Critical takeaways:

  • Default to cross-origin for public assets, restrict only sensitive resources
  • Monitor console errors and use DevTools to debug blocked requests
  • Roll out CORP gradually with monitoring before enforcement

Next steps: 🎯 Audit your application's resources and classify by sensitivity level πŸ”§ Implement CORP headers with a phased approach starting with high-risk endpoints πŸ“š Explore COEP/COOP integration for applications requiring process isolation