Browser Security Model: SOP, CSP, CORS, and Cross-Origin Isolation

Type: Software Reference Confidence: 0.95 Sources: 7 Verified: 2026-02-27 Freshness: 2026-02-27

TL;DR

Constraints

Quick Reference

Browser Security Policies Comparison

#Policy/HeaderPurposeScopeDefault BehaviorBypass Risk
1Same-Origin Policy (SOP)Restricts cross-origin DOM access, XHR/fetch reads, and storageBrowser-enforced on all originsBlocks cross-origin reads; allows embeds and writesMisconfigured CORS, document.domain (deprecated), postMessage without origin check
2Content Security Policy (CSP)Controls which resources (scripts, styles, images) a page can loadPer-page via HTTP header or <meta>No restrictions (everything allowed)unsafe-inline, unsafe-eval, overly broad allowlists, script gadgets
3Cross-Origin Resource Sharing (CORS)Allows servers to opt in to cross-origin requestsPer-resource via response headersBrowsers block cross-origin fetch/XHR responsesWildcard origin with credentials, reflected Origin header, null origin trust
4Cross-Origin-Opener-Policy (COOP)Isolates browsing context group from cross-origin openersPer-document via response headerunsafe-none (no isolation)Breaking popup-based OAuth/payment flows with same-origin
5Cross-Origin-Embedder-Policy (COEP)Requires subresources to opt in via CORS/CORPPer-document via response headerunsafe-none (no restrictions)Third-party resources without CORP/CORS headers get blocked
6Cross-Origin-Resource-Policy (CORP)Controls which origins can embed a resourcePer-resource via response headerResources loadable from any originBlocks legitimate cross-origin embeds if set too restrictively
7Permissions-PolicyRestricts browser features (camera, geolocation, etc.)Per-page and per-iframeAll features available to top-levelOverly permissive allow on iframes
8Referrer-PolicyControls what referrer info is sent with requestsPer-page or per-requeststrict-origin-when-cross-origin (Chrome 85+ default)no-referrer-when-downgrade leaks full URL on HTTPS-to-HTTPS navigations

Origin Definition (SOP Foundation)

ComponentSame-Origin ExampleCross-Origin Example
Schemehttps://a.com = https://a.comhttp://a.com != https://a.com
Hosta.com = a.coma.com != b.a.com
Porta.com:443 = a.com:443a.com:443 != a.com:8443

Decision Tree

START: What is your cross-origin security goal?
├── Need to BLOCK cross-origin data reads?
│   ├── YES → SOP handles this by default. No action needed.
│   └── NO ↓
├── Need to ALLOW cross-origin API access?
│   ├── YES → Configure CORS headers on the server (Access-Control-Allow-Origin)
│   └── NO ↓
├── Need to prevent inline script injection / restrict resource loading?
│   ├── YES → Deploy Content Security Policy (CSP) with nonce-based script-src
│   └── NO ↓
├── Need SharedArrayBuffer or high-res timers (cross-origin isolation)?
│   ├── YES → Set COOP: same-origin + COEP: require-corp on the document
│   └── NO ↓
├── Need to restrict browser features in embedded iframes?
│   ├── YES → Use Permissions-Policy header + iframe allow attribute
│   └── NO ↓
├── Need to limit referrer information leakage?
│   ├── YES → Set Referrer-Policy: strict-origin-when-cross-origin (or stricter)
│   └── NO ↓
└── DEFAULT → Deploy all security headers: CSP + COOP + COEP + Permissions-Policy + Referrer-Policy

Step-by-Step Guide

1. Audit current security headers

Check which headers your site already sends. Missing headers mean the browser uses permissive defaults. [src1]

# Audit all browser security headers at once
curl -sI https://your-site.com | grep -iE \
  'content-security|access-control|cross-origin|referrer-policy|permissions-policy|x-frame|strict-transport'

Verify: Every key header should appear in the output: Content-Security-Policy, Cross-Origin-Opener-Policy, Cross-Origin-Embedder-Policy, Referrer-Policy, Permissions-Policy.

2. Deploy a strict Content Security Policy

CSP is the most impactful single header -- it mitigates XSS, data injection, and clickjacking. Use nonce-based policies for best protection. [src3]

Content-Security-Policy:
  default-src 'self';
  script-src 'nonce-{random}' 'strict-dynamic';
  style-src 'self' 'nonce-{random}';
  img-src 'self' https:;
  font-src 'self';
  connect-src 'self' https://api.your-domain.com;
  object-src 'none';
  base-uri 'none';
  frame-ancestors 'none';
  form-action 'self';
  upgrade-insecure-requests;

Verify: Inject <script>alert(1)</script> -- CSP should block it. Check DevTools Console for CSP violation reports.

3. Configure CORS on your API server

Set specific origins -- never reflect the Origin header blindly. [src2]

// Node.js/Express CORS configuration
const cors = require('cors');  // ^2.8.0
const corsOptions = {
  origin: ['https://your-app.com', 'https://staging.your-app.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400  // Preflight cache: 24 hours
};
app.use('/api', cors(corsOptions));

Verify: curl -H "Origin: https://evil.com" -sI https://api.your-site.com/endpoint -- should NOT return Access-Control-Allow-Origin: https://evil.com.

4. Enable cross-origin isolation (COOP + COEP)

Required for SharedArrayBuffer and high-resolution timers. Test in report-only mode first. [src5]

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

For pages needing OAuth popups or payment integrations:

Cross-Origin-Opener-Policy: same-origin-allow-popups
Cross-Origin-Embedder-Policy: credentialless

Verify: Open DevTools Console and check self.crossOriginIsolated === true.

5. Set Permissions-Policy and Referrer-Policy

Restrict unnecessary browser features and control referrer leakage. [src1]

Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(self),
  usb=(), magnetometer=(), gyroscope=(), accelerometer=()

Referrer-Policy: strict-origin-when-cross-origin

Verify: document.featurePolicy.allowsFeature('camera') should return false in DevTools Console.

Code Examples

Node.js/Express: Complete Security Headers Middleware

const crypto = require('crypto');

function securityHeaders(req, res, next) {
  const nonce = crypto.randomBytes(16).toString('base64');
  res.locals.cspNonce = nonce;

  res.set({
    'Content-Security-Policy':
      `default-src 'self'; script-src 'nonce-${nonce}' 'strict-dynamic'; ` +
      `style-src 'self' 'nonce-${nonce}'; object-src 'none'; base-uri 'none'; ` +
      `frame-ancestors 'none'; form-action 'self'; upgrade-insecure-requests`,
    'Cross-Origin-Opener-Policy': 'same-origin',
    'Cross-Origin-Embedder-Policy': 'require-corp',
    'Referrer-Policy': 'strict-origin-when-cross-origin',
    'Permissions-Policy': 'camera=(), microphone=(), geolocation=()',
    'X-Content-Type-Options': 'nosniff',
    'X-Frame-Options': 'DENY'
  });
  next();
}

Nginx: Security Headers Configuration

# /etc/nginx/snippets/security-headers.conf
add_header Content-Security-Policy
  "default-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'"
  always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy
  "camera=(), microphone=(), geolocation=(), payment=(self)"
  always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

Python/Django: Middleware Security Headers

# settings.py -- Django 4.x+ built-in security middleware
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_CROSS_ORIGIN_OPENER_POLICY = "same-origin"
SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"
X_FRAME_OPTIONS = "DENY"

# CSP via django-csp (^4.0)
CSP_DEFAULT_SRC = ("'self'",)
CSP_SCRIPT_SRC = ("'self'",)
CSP_OBJECT_SRC = ("'none'",)
CSP_BASE_URI = ("'none'",)
CSP_FRAME_ANCESTORS = ("'none'",)

# CORS via django-cors-headers (^4.0)
CORS_ALLOWED_ORIGINS = [
    "https://your-frontend.com",
    "https://staging.your-frontend.com",
]
CORS_ALLOW_CREDENTIALS = True

Go: Security Headers Middleware

package main

import "net/http"

func securityHeaders(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Security-Policy",
            "default-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'")
        w.Header().Set("Cross-Origin-Opener-Policy", "same-origin")
        w.Header().Set("Cross-Origin-Embedder-Policy", "require-corp")
        w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
        w.Header().Set("Permissions-Policy", "camera=(), microphone=(), geolocation=()")
        w.Header().Set("X-Content-Type-Options", "nosniff")
        w.Header().Set("X-Frame-Options", "DENY")
        next.ServeHTTP(w, r)
    })
}

Anti-Patterns

Wrong: Reflecting the Origin header in CORS

// BAD -- reflects any origin, equivalent to disabling SOP
app.use((req, res, next) => {
  res.set('Access-Control-Allow-Origin', req.headers.origin);
  res.set('Access-Control-Allow-Credentials', 'true');
  next();
});
// Attacker on https://evil.com can read authenticated responses

Correct: Allowlist specific origins

// GOOD -- only trusted origins can make credentialed requests
const ALLOWED = new Set(['https://app.example.com', 'https://staging.example.com']);

app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (ALLOWED.has(origin)) {
    res.set('Access-Control-Allow-Origin', origin);
    res.set('Access-Control-Allow-Credentials', 'true');
    res.set('Vary', 'Origin');  // Critical for caching
  }
  next();
});

Wrong: CSP with unsafe-inline and unsafe-eval

# BAD -- defeats the entire purpose of CSP
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'

Correct: Nonce-based CSP with strict-dynamic

# GOOD -- nonces block injected scripts, strict-dynamic trusts loader chains
Content-Security-Policy: default-src 'self'; script-src 'nonce-abc123' 'strict-dynamic'

Wrong: Trusting the null origin in CORS

// BAD -- null origin comes from sandboxed iframes, data: URLs, redirects
app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (origin === 'null') {
    res.set('Access-Control-Allow-Origin', 'null');
  }
  next();
});
// Attacker can forge null origin via sandboxed iframe

Correct: Never trust null origin

// GOOD -- reject null and only trust explicit HTTPS origins
const ALLOWED = new Set(['https://app.example.com']);

app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (origin && ALLOWED.has(origin)) {
    res.set('Access-Control-Allow-Origin', origin);
    res.set('Vary', 'Origin');
  }
  next();
});

Wrong: Missing Vary: Origin on CORS responses

// BAD -- CDN caches response for one origin, serves it to all
res.set('Access-Control-Allow-Origin', req.headers.origin);
// Missing: res.set('Vary', 'Origin');

Correct: Always set Vary: Origin

// GOOD -- ensures CDN/proxy caches differentiate by origin
res.set('Access-Control-Allow-Origin', origin);
res.set('Vary', 'Origin');

Common Pitfalls

Diagnostic Commands

# Audit all security headers at once
curl -sI https://your-site.com | grep -iE \
  'content-security|access-control|cross-origin|referrer-policy|permissions-policy|x-frame|strict-transport'

# Test CORS: check if an origin is allowed
curl -H "Origin: https://app.example.com" -sI https://api.example.com/endpoint \
  | grep -i access-control

# Test CORS preflight (OPTIONS request)
curl -X OPTIONS \
  -H "Origin: https://app.example.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Content-Type, Authorization" \
  -sI https://api.example.com/endpoint

# Check cross-origin isolation status (in browser console)
# self.crossOriginIsolated  // should be true

# Validate CSP: visit https://csp-evaluator.withgoogle.com/

# Scan security headers: visit https://securityheaders.com/?q=https://your-site.com

# Test for CORS misconfiguration (evil origin should be rejected)
curl -H "Origin: https://evil.com" -sI https://your-site.com/api \
  | grep -i access-control-allow-origin

Version History & Compatibility

Standard/FeatureStatusChromeFirefoxSafariEdge
Same-Origin Policy (SOP)FoundationalAllAllAllAll
CSP Level 2W3C Rec40+31+10+15+
CSP Level 3 (strict-dynamic, nonces)W3C WD59+58+15.4+79+
CORS (basic)W3C Rec4+3.5+4+12+
COOP (same-origin)Standard83+79+15.2+83+
COEP (require-corp)Standard83+79+15.2+83+
COEP (credentialless)Standard96+119+No96+
Cross-Origin-Resource-Policy (CORP)Standard73+74+12+79+
Permissions-PolicyStandard88+65+ (partial)No88+
Referrer-PolicyStandard56+50+11.1+79+
Site IsolationChrome-specific67+ (desktop)Fission (95+)Process per tab67+
Trusted TypesDraft83+Behind flagNo83+

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Your API serves multiple frontend originsAPI and frontend share the same originSOP already blocks unauthorized access -- CORS not needed
Preventing inline script injection (XSS defense layer)You need sole XSS preventionCSP alone is insufficient -- add output encoding (see XSS Prevention unit)
App uses SharedArrayBuffer, WASM threads, or high-res timersApp is a simple content site with no advanced APIsCOOP/COEP add complexity with no benefit
Embedding third-party iframes that should not access camera/micTop-level page controls all features itselfPermissions-Policy is most valuable for iframe restrictions
Hosting resources that should only load on your own siteHosting public CDN resources meant for cross-origin useSet CORP: cross-origin for public resources

Important Caveats

Related Units