11import { afterEach , beforeEach , describe , expect , it , vi } from "vitest" ;
22import type { SessionMcpRuntime } from "./agent-bundle-mcp-types.js" ;
33import { updateMcpAppModelContext } from "./mcp-app-model-context.js" ;
4- import {
5- buildMcpAppContentSecurityPolicy ,
6- buildMcpAppSandboxPath ,
7- buildMcpAppSandboxProxyHtml ,
8- decodeMcpAppSandboxCsp ,
9- resolveMcpAppSandboxPort ,
10- } from "./mcp-app-sandbox.js" ;
4+ import { buildMcpAppSandboxPath , resolveMcpAppSandboxPort } from "./mcp-app-sandbox.js" ;
115import {
126 acquireMcpAppViewRequest ,
137 fetchMcpAppView ,
@@ -161,7 +155,7 @@ describe("MCP App UI resources", () => {
161155 releases . slice ( 1 ) . forEach ( ( entry ) => entry ( ) ) ;
162156 } ) ;
163157
164- it ( "injects a restrictive CSP and drops invalid metadata origins " , async ( ) => {
158+ it ( "normalizes CSP metadata before retaining the view " , async ( ) => {
165159 const sessionRuntime = runtime ( async ( ) => ( {
166160 contents : [
167161 {
@@ -188,50 +182,12 @@ describe("MCP App UI resources", () => {
188182 toolResult : { content : [ ] } ,
189183 } ) ;
190184 const view = getMcpAppViewLease ( result ?. viewId ?? "" , sessionRuntime ) ;
191- const policy = buildMcpAppContentSecurityPolicy ( view ?. csp ) ;
192-
193- expect ( policy ) . toContain ( "connect-src https://api.example.com" ) ;
194- expect ( policy ) . toContain ( "script-src 'self' 'unsafe-inline' https://cdn.example.com" ) ;
195- expect ( policy ) . toContain ( "font-src 'self' https://cdn.example.com" ) ;
196- expect ( policy ) . not . toContain ( "worker-src" ) ;
197- expect ( policy ) . not . toContain ( "script-src 'self' 'unsafe-inline' blob:" ) ;
198- expect ( policy ) . toContain ( "base-uri 'self'" ) ;
199- expect ( policy ) . not . toContain ( "javascript:alert" ) ;
185+ expect ( view ?. csp ) . toEqual ( {
186+ connectDomains : [ "https://api.example.com" ] ,
187+ resourceDomains : [ "https://cdn.example.com" ] ,
188+ } ) ;
200189 expect ( view ?. html . startsWith ( "<!doctype html>" ) ) . toBe ( true ) ;
201- expect ( policy ) . toContain ( "frame-ancestors http: https:" ) ;
202- const sandboxPath = buildMcpAppSandboxPath ( view ?. csp ) ;
203- const encodedCsp = new URL ( sandboxPath , "https://gateway.example" ) . searchParams . get ( "csp" ) ;
204- expect ( decodeMcpAppSandboxCsp ( encodedCsp ) ) . toStrictEqual ( view ?. csp ) ;
205- } ) ;
206-
207- it . each ( [ "bm90LWpzb24" , Buffer . from ( "{not json}" , "utf8" ) . toString ( "base64url" ) ] ) (
208- "rejects malformed encoded CSP metadata" ,
209- ( value ) => {
210- expect ( ( ) => decodeMcpAppSandboxCsp ( value ) ) . toThrow ( ) ;
211- } ,
212- ) ;
213-
214- it ( "rejects CSP metadata with invalid UTF-8 instead of silently narrowing it" , ( ) => {
215- const value = Buffer . concat ( [
216- Buffer . from ( '{"connectDomains":["https://api.example.com","https://cdn-' ) ,
217- Buffer . from ( [ 0xff ] ) ,
218- Buffer . from ( '.example.com"]}' ) ,
219- ] ) . toString ( "base64url" ) ;
220- // A forgiving decode would drop the corrupted domain and accept the rest of
221- // the policy, violating the malformed-input-must-throw contract.
222- expect ( ( ) => decodeMcpAppSandboxCsp ( value ) ) . toThrow ( ) ;
223- } ) ;
224-
225- it ( "builds proxy HTML" , ( ) => {
226- const proxyHtml = buildMcpAppSandboxProxyHtml ( ) ;
227- expect ( proxyHtml ) . toContain ( 'frame.setAttribute("sandbox", "allow-scripts allow-forms")' ) ;
228- expect ( proxyHtml ) . toContain ( "nextInner.srcdoc = guardDocument(params.html)" ) ;
229- expect ( proxyHtml ) . not . toContain ( "doc.write" ) ;
230- expect ( proxyHtml ) . not . toContain ( "params.sandbox" ) ;
231- expect ( proxyHtml ) . not . toContain ( "params.permissions" ) ;
232- expect ( proxyHtml ) . toContain ( "document.referrer" ) ;
233- expect ( proxyHtml ) . not . toContain ( "hostOrigin === null" ) ;
234- expect ( proxyHtml ) . toContain ( 'startsWith("ui/notifications/sandbox-")' ) ;
190+ expect ( buildMcpAppSandboxPath ( view ?. csp ) ) . toContain ( "?csp=" ) ;
235191 } ) ;
236192
237193 it ( "deletes sensitive view data when the lease expires without later activity" , async ( ) => {
@@ -276,7 +232,7 @@ describe("MCP App UI resources", () => {
276232 ) ;
277233 const path = buildMcpAppSandboxPath ( { connectDomains : shortDomains } ) ;
278234 const encoded = new URL ( path , "https://gateway.example" ) . searchParams . get ( "csp" ) ;
279- expect ( decodeMcpAppSandboxCsp ( encoded ) ?. connectDomains ) . toStrictEqual ( shortDomains ) ;
235+ expect ( encoded ) . toBeTruthy ( ) ;
280236
281237 const domains = Array . from (
282238 { length : 64 } ,
@@ -292,18 +248,6 @@ describe("MCP App UI resources", () => {
292248 ) . toThrow ( "MCP App CSP metadata exceeds safe HTTP limits" ) ;
293249 } ) ;
294250
295- it ( "uses the stable restrictive CSP when metadata is omitted" , ( ) => {
296- const policy = buildMcpAppContentSecurityPolicy ( ) ;
297- expect ( policy ) . toContain ( "default-src 'none'" ) ;
298- expect ( policy ) . toContain ( "script-src 'self' 'unsafe-inline'" ) ;
299- expect ( policy ) . toContain ( "style-src 'self' 'unsafe-inline'" ) ;
300- expect ( policy ) . toContain ( "img-src 'self' data:" ) ;
301- expect ( policy ) . toContain ( "media-src 'self' data:" ) ;
302- expect ( policy ) . toContain ( "connect-src 'none'" ) ;
303- expect ( policy ) . not . toMatch ( / \b (?: b l o b | f o n t | w o r k e r ) - s r c \b / u) ;
304- expect ( policy ) . not . toContain ( "blob:" ) ;
305- } ) ;
306-
307251 it ( "derives a distinct listener port without wrapping" , ( ) => {
308252 expect ( resolveMcpAppSandboxPort ( 18789 ) ) . toBe ( 18790 ) ;
309253 expect ( resolveMcpAppSandboxPort ( 18789 , 29000 ) ) . toBe ( 29000 ) ;
0 commit comments