From 7ab392741dc02150c809536b653cf594f06e879f Mon Sep 17 00:00:00 2001 From: Soumyajit2288 Date: Sat, 11 Jul 2026 13:18:35 +0530 Subject: [PATCH] fix: restrict HTML preview network access --- .../file-preview/src/components/html-renderer.ts | 15 +++++++++++++-- test/test-html-preview-csp.js | 12 ++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 test/test-html-preview-csp.js diff --git a/src/ui/file-preview/src/components/html-renderer.ts b/src/ui/file-preview/src/components/html-renderer.ts index b0369ed4..7b618518 100644 --- a/src/ui/file-preview/src/components/html-renderer.ts +++ b/src/ui/file-preview/src/components/html-renderer.ts @@ -4,12 +4,23 @@ * * The rendered preview runs inside a nested sandboxed iframe, which is itself inside * the MCP app's sandboxed iframe chain. Scripts and external resources (CDNs) are - * allowed since the sandbox isolation prevents any escape. + * allowed for inline interactivity, while CSP blocks outbound requests. */ import { renderCodeViewer } from './code-viewer.js'; import { escapeHtml } from './highlighting.js'; import type { HtmlPreviewMode } from '../types.js'; +const HTML_PREVIEW_CSP = [ + "default-src 'none'", + "script-src 'unsafe-inline'", + "style-src 'unsafe-inline'", + 'img-src data: blob:', + 'media-src data: blob:', + "connect-src 'none'", + "form-action 'none'", + "base-uri 'none'", +].join('; '); + function resolveThemeFrameStyles(): { background: string; text: string; fontFamily: string } { if (typeof window === 'undefined' || typeof document === 'undefined') { return { @@ -27,7 +38,7 @@ function resolveThemeFrameStyles(): { background: string; text: string; fontFami function renderSandboxedHtmlFrame(content: string): string { const palette = resolveThemeFrameStyles(); - const frameDocument = `${content}`; + const frameDocument = `${content}`; return ``; } diff --git a/test/test-html-preview-csp.js b/test/test-html-preview-csp.js new file mode 100644 index 00000000..7f9c46c5 --- /dev/null +++ b/test/test-html-preview-csp.js @@ -0,0 +1,12 @@ +import assert from 'assert'; +import { renderHtmlPreview } from '../dist/ui/file-preview/src/components/html-renderer.js'; + +const rendered = renderHtmlPreview('', 'rendered'); + +assert.ok(rendered.html.includes('Content-Security-Policy')); +assert.ok(rendered.html.includes('connect-src 'none'')); +assert.ok(rendered.html.includes('form-action 'none'')); +assert.ok(rendered.html.includes('sandbox="allow-scripts allow-forms allow-popups"')); +assert.ok(rendered.html.includes('<script>fetch("https://example.com")</script>')); + +console.log('PASS: rendered HTML previews carry a restrictive CSP');