@@ -253,14 +253,32 @@ export class ChatMcpAppModel extends Disposable {
253253 * Injects a Content-Security-Policy meta tag into the HTML.
254254 */
255255 private _injectPreamble ( { html, csp } : IMcpAppResourceContent ) : string {
256+ // Note: this is not bulletproof against malformed domains. However it does not
257+ // need to be. The server is the one giving us both the CSP as well as the HTML
258+ // to render in the iframe. MCP Apps give the CSP separately so that systems that
259+ // proxy the HTML from a server can set it in a header, but the CSP and the HTML
260+ // come from the same source and are within the same trust boundary. We only
261+ // process the CSP enough (escaping HTML special characters) to avoid breaking it.
262+ //
263+ // It would certainly be more durable to use `DOMParser.parseFromString` here
264+ // and operate on the DocumentFragment of the HTML, however (even though keeping
265+ // it solely as a detached document is safe) this requires making the HTML trusted
266+ // in the renderer and bypassing various tsec warnings. I consider the string
267+ // munging here to be the lesser of two evils.
268+ const cleanDomains = ( s : string [ ] | undefined ) => ( s ?. join ( ' ' ) || '' )
269+ . replaceAll ( '&' , '&' )
270+ . replaceAll ( '<' , '<' )
271+ . replaceAll ( '>' , '>' )
272+ . replaceAll ( '"' , '"' ) ;
273+
256274 const cspContent = `
257275 default-src 'none';
258276 script-src 'self' 'unsafe-inline';
259277 style-src 'self' 'unsafe-inline';
260- connect-src 'self' ${ csp ?. connectDomains ?. join ( ' ' ) || '' } ;
261- img-src 'self' data: ${ csp ?. resourceDomains ?. join ( ' ' ) || '' } ;
262- font-src 'self' ${ csp ?. resourceDomains ?. join ( ' ' ) || '' } ;
263- media-src 'self' data: ${ csp ?. resourceDomains ?. join ( ' ' ) || '' } ;
278+ connect-src 'self' ${ cleanDomains ( csp ?. connectDomains ) } ;
279+ img-src 'self' data: ${ cleanDomains ( csp ?. resourceDomains ) } ;
280+ font-src 'self' ${ cleanDomains ( csp ?. resourceDomains ) } ;
281+ media-src 'self' data: ${ cleanDomains ( csp ?. resourceDomains ) } ;
264282 frame-src 'none';
265283 object-src 'none';
266284 base-uri 'self';
0 commit comments