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 value | Who can read the response | Use 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
Cross-Origin-Resource-Policy: cross-origin on MCP API endpoint — explicitly allows any origin to read authenticated API responsesCross-Origin-Resource-Policy: same-site on MCP API — compromised or XSS'd subdomain on the same registrable domain can read MCP tool outputSee 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 →