Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 38 additions & 43 deletions mcpjam-inspector/server/routes/apps/mcp-apps/sandbox-proxy.html
Original file line number Diff line number Diff line change
Expand Up @@ -91,18 +91,33 @@
* @returns {string} CSP policy string
*/
function buildCSP(csp) {
// Inspector is a dev tool — auto-allow localhost connections.
// In srcdoc iframes, 'self' resolves to about:srcdoc (useless),
// so we must explicitly allow localhost/127.0.0.1 for dev server
// connections (Vite HMR, API server, local MCP servers, etc.)
var localhostSrc = [
"http://localhost:*",
"https://localhost:*",
"http://127.0.0.1:*",
"https://127.0.0.1:*",
].join(" ");
var localhostConnectSrc =
localhostSrc +
" ws://localhost:* wss://localhost:* ws://127.0.0.1:* wss://127.0.0.1:*";

// Per SEP-1865: If no CSP declared, use restrictive defaults
// Note: 'self' doesn't work in srcdoc iframes (refers to about:srcdoc)
// So we use 'unsafe-inline' for scripts/styles since all widget code is inline
if (!csp) {
return [
"default-src 'none'",
"script-src 'unsafe-inline'",
"script-src 'unsafe-inline' 'unsafe-eval'",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

'unsafe-eval' in the "restrictive defaults" path undermines the stated intent.

When no CSP is declared by the MCP app, the comment on line 108 says "use restrictive defaults." Adding 'unsafe-eval' here contradicts that — it allows eval(), Function(), and setTimeout(string) in guest code that declared no CSP at all. The Vite HMR rationale doesn't apply when the widget hasn't opted into any external domains.

Consider removing 'unsafe-eval' from this no-CSP branch and keeping it only in the declared-CSP path (line 165), where the developer has explicitly configured connectivity.

Proposed fix
-            "script-src 'unsafe-inline' 'unsafe-eval'",
+            "script-src 'unsafe-inline'",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"script-src 'unsafe-inline' 'unsafe-eval'",
"script-src 'unsafe-inline'",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcpjam-inspector/server/routes/apps/mcp-apps/sandbox-proxy.html` at line 114,
The "no-CSP" (restrictive defaults) branch currently includes "script-src
'unsafe-inline' 'unsafe-eval'", which undermines the intent by allowing eval;
remove "'unsafe-eval'" from that string so the no-CSP/defaults path becomes
"script-src 'unsafe-inline'". Keep "'unsafe-eval'" only in the declared-CSP
branch where the code already constructs the CSP for apps that opted in (the
branch that currently adds "'unsafe-eval'"/Vite HMR support).

"style-src 'unsafe-inline'",
"img-src data:",
"font-src data:",
"media-src data:",
"connect-src 'none'",
"img-src data: " + localhostSrc,
"font-src data: " + localhostSrc,
"media-src data: " + localhostSrc,
"worker-src blob: " + localhostSrc,
"connect-src " + localhostConnectSrc,
"frame-src 'none'",
"object-src 'none'",
"base-uri 'none'",
Expand All @@ -111,68 +126,48 @@

// Build CSP from declared domains (SEP-1865)
// Per spec: "Host MAY further restrict but MUST NOT allow undeclared domains"
const connectDomains = (csp.connectDomains || [])
var connectDomains = (csp.connectDomains || [])
.map(sanitizeDomain)
.filter(Boolean);
const resourceDomains = (csp.resourceDomains || [])
var resourceDomains = (csp.resourceDomains || [])
.map(sanitizeDomain)
.filter(Boolean);
const frameDomains = (csp.frameDomains || [])
var frameDomains = (csp.frameDomains || [])
.map(sanitizeDomain)
.filter(Boolean);
const baseUriDomains = (csp.baseUriDomains || [])
var baseUriDomains = (csp.baseUriDomains || [])
.map(sanitizeDomain)
.filter(Boolean);

// connect-src: Only allow declared domains, or 'none' if empty
const connectSrc =
connectDomains.length > 0 ? connectDomains.join(" ") : "'none'";
// connect-src: declared domains + localhost (dev tool)
var connectSrc =
connectDomains.length > 0
? localhostConnectSrc + " " + connectDomains.join(" ")
: localhostConnectSrc;

// Resource sources: data: and blob: are always allowed for inline content
// Only add declared resourceDomains - no forced CDNs per SEP-1865
const resourceSrc =
// Resource sources: data:, blob:, and localhost always allowed
// Plus declared resourceDomains
var resourceSrc =
resourceDomains.length > 0
? ["data:", "blob:", ...resourceDomains].join(" ")
: "data: blob:";
? ["data:", "blob:", localhostSrc, ...resourceDomains].join(" ")
: "data: blob: " + localhostSrc;

// frame-src: Only allow declared frame domains, or 'none' if empty
const frameSrc =
var frameSrc =
frameDomains.length > 0 ? frameDomains.join(" ") : "'none'";

// base-uri: Only allow declared base URI domains, or 'none' if empty
const baseUri =
var baseUri =
baseUriDomains.length > 0 ? baseUriDomains.join(" ") : "'none'";

console.log("[buildCSP] Processing domains:", {
frameDomains,
frameSrc,
baseUriDomains,
baseUri,
});

console.log(
"[buildCSP] Built CSP string:",
[
"default-src 'none'",
"script-src 'unsafe-inline' " + resourceSrc,
"style-src 'unsafe-inline' " + resourceSrc,
"img-src " + resourceSrc,
"font-src " + resourceSrc,
"media-src " + resourceSrc,
"connect-src " + connectSrc,
"frame-src " + frameSrc,
"object-src 'none'",
"base-uri " + baseUri,
].join("; "),
);

return [
"default-src 'none'",
"script-src 'unsafe-inline' " + resourceSrc,
"script-src 'unsafe-inline' 'unsafe-eval' " + resourceSrc,
"style-src 'unsafe-inline' " + resourceSrc,
"img-src " + resourceSrc,
"font-src " + resourceSrc,
"media-src " + resourceSrc,
"worker-src blob: " + resourceSrc,
"connect-src " + connectSrc,
"frame-src " + frameSrc,
"object-src 'none'",
Expand Down