MCP Server Security · Cross-Origin Resource Policy

MCP server Cross-Origin Resource Policy — CORP headers to block cross-origin reads, Spectre mitigations, and COEP combination

Cross-Origin Resource Policy (CORP) is an HTTP response header that prevents browsers from loading a resource from a cross-origin context. For MCP servers, it means a cross-origin attacker page cannot use <img>, <script>, or fetch() to read your MCP API responses — even as a Spectre timing side channel. CORP is also a prerequisite for cross-origin isolation: pages that want to re-enable SharedArrayBuffer must set Cross-Origin-Embedder-Policy: require-corp, which in turn requires every subresource to opt in via Cross-Origin-Resource-Policy.

The three CORP values and when to use each

Header valueWho can read the responseUse for
same-origin Same origin only (exact scheme + host + port match) MCP API endpoints, admin UI pages, any response containing sensitive data or authentication state
same-site Same registrable domain (e.g., all of *.example.com) API endpoints that need to be called from multiple subdomains on the same eTLD+1. Use with caution — a compromised subdomain can read responses.
cross-origin Any origin Publicly embeddable assets: fonts, public images, CDN-hosted scripts that need to be loaded by third-party sites. Required for COEP to not block these.

For MCP server API endpoints, the correct value is always same-origin:

# Caddy: add CORP header to all MCP API responses
route /mcp/* {
  header Cross-Origin-Resource-Policy "same-origin"
  reverse_proxy localhost:3000
}

# For the static admin UI assets served alongside the API
route /admin/* {
  header Cross-Origin-Resource-Policy "same-origin"
  file_server
}

Why CORP matters for Spectre cross-process memory reads

Spectre side-channel attacks against browsers work by loading a resource into the renderer process's memory and then using timing to read that memory from a speculative execution path. The browser normally prevents JavaScript from reading cross-origin responses (the Same-Origin Policy), but Spectre can extract bytes from a response that was merely loaded into the process — even if JavaScript never sees the Response object.

CORP prevents the resource from being loaded into the cross-origin renderer process at all. With Cross-Origin-Resource-Policy: same-origin, the browser refuses to load the MCP API response into any process other than same-origin ones, closing the Spectre read surface.

CORP without COOP still leaks timing. CORP prevents the response body from being loaded into cross-origin process memory. But if your MCP server's responses have measurable timing variation based on the authenticated user's data, a timing oracle attack is still possible even without Spectre. CORP is one layer; COOP (which isolates the renderer process itself) is the complementary layer.

CORP + COEP: the combination required for SharedArrayBuffer

When a page sets Cross-Origin-Embedder-Policy: require-corp, the browser requires every resource it loads (scripts, styles, images, API fetch calls) to have a CORP header explicitly granting access. This is how COEP creates a fully isolated process: nothing from an external origin can sneak into the page without explicitly opting in.

// Server-side: pages that need SharedArrayBuffer
// Must set BOTH of these headers
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

// Every resource loaded by such pages must also respond with:
// (for resources that are legitimately cross-origin, e.g. your own CDN)
Cross-Origin-Resource-Policy: cross-origin

// For same-origin resources (your MCP API, admin assets):
Cross-Origin-Resource-Policy: same-origin  // or omit — same-origin is implied

// For resources that must remain cross-origin but can't add CORP headers
// (e.g., third-party analytics) — use credentialless COEP instead:
Cross-Origin-Embedder-Policy: credentialless
// Loads cross-origin resources without cookies/credentials — they see no user state
// Does not require CORP on third-party resources, but SharedArrayBuffer is still available

Practical deployment: Node.js middleware

// Express middleware to set CORP on all MCP API responses
function corpMiddleware(req, res, next) {
  const path = req.path;

  if (path.startsWith('/mcp/') || path.startsWith('/api/')) {
    // MCP API: restrict to same-origin only
    res.setHeader('Cross-Origin-Resource-Policy', 'same-origin');
  } else if (path.startsWith('/assets/public/')) {
    // Public CDN-candidate assets: allow cross-origin embedding
    res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
  } else {
    // Default: same-origin for everything else
    res.setHeader('Cross-Origin-Resource-Policy', 'same-origin');
  }

  next();
}

SkillAudit findings

CRITICAL −20MCP API responses carry no CORP header — cross-origin pages can use Spectre timing to read response bytes from renderer process memory
HIGH −16COEP set to require-corp on admin UI but MCP API endpoint missing CORP header — COEP blocks all API calls, breaking the UI
HIGH −14Cross-Origin-Resource-Policy: cross-origin on MCP API endpoint — explicitly allows any origin to read authenticated API responses
MEDIUM −10Cross-Origin-Resource-Policy: same-site on MCP API — compromised or XSS'd subdomain on the same registrable domain can read MCP tool output
MEDIUM −8CORP header absent on tool output endpoints used by browser-based MCP clients — required for cross-origin isolation if admin UI needs SharedArrayBuffer

See also: SharedArrayBuffer and COOP/COEP · Content Security Policy · CORS preflight security

Audit your MCP server for CORP configuration

SkillAudit checks for missing CORP headers on API endpoints, COEP/CORP mismatches, and same-site vs same-origin policy gaps.

Run free audit →