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;
onerrorevent 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-sitepolicies 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-originfor 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