@@ -33,6 +33,11 @@ export interface ContentSecurityPolicyMiddlewareOptions {
3333 playgroundPreviewOrigin : string ;
3434}
3535
36+ /**
37+ * A Google service which aggregates CSP violations.
38+ */
39+ const CSP_REPORT_URI = 'https://csp.withgoogle.com/csp/lit-dev' ;
40+
3641/**
3742 * Creates a Koa middleware that sets the lit.dev Content Security Policy (CSP)
3843 * headers.
@@ -46,31 +51,22 @@ export interface ContentSecurityPolicyMiddlewareOptions {
4651export const contentSecurityPolicyMiddleware = (
4752 opts : ContentSecurityPolicyMiddlewareOptions
4853) : Koa . Middleware => {
49- const cspHeaderValue = [
54+ const mainCsp = [
5055 // TODO(aomarks) We should also enable trusted types, but that will require
5156 // a policy in playground-elements for creating the worker, and a policy
5257 // es-module-lexer for doing an eval (see next comment for more on that).
5358
54- // TODO(aomarks) unsafe-eval is needed for an eval that is made by
55- // es-module-lexer to perform JavaScript string unescaping
56- // (https://github.com/guybedford/es-module-lexer/blob/91964da6b086dc5029091eeef481180a814ce24a/src/lexer.js#L32).
57- // There are a number of ways we could make this stricter: [1] use the
58- // asm.js build which doesn't use eval (but it's significantly slower), [2]
59- // use a separate CSP for the worker (except Chrome doesn't support that
60- // yet, though we could simulate it with an iframe), [3] get trusted types
61- // into the WASM build, [4] modify or fork the WASM build to implement
62- // string unescaping without eval (needs benchmarking).
59+ // TODO(aomarks) Remove unsafe-eval when https://crbug.com/1253267 is fixed.
60+ // See comment below about playgroundWorkerCsp.
6361 //
6462 // In dev mode, data: scripts are required because @web/dev-server uses them
6563 // for automatic reloads.
6664 `script-src 'self' 'unsafe-eval' ${
6765 opts . inlineScriptHashes ?. map ( ( hash ) => `'${ hash } '` ) . join ( ' ' ) ?? ''
6866 } https://www.googletagmanager.com/gtag/js ${ opts . devMode ? ` data:` : '' } `,
6967
70- // unpkg.com is needed to allow the Playground worker to fetch dependencies.
71- // TODO(aomarks) After https://crbug.com/1253267 is fixed we can serve a
72- // separate CSP policy just for the worker script (Firefox and Safari
73- // already support this).
68+ // TODO(aomarks) Remove unpkg.com when https://crbug.com/1253267 is fixed.
69+ // See comment below about playgroundWorkerCsp.
7470 //
7571 // In dev mode, ws: connections are required because @web/dev-server uses
7672 // them for automatic reloads.
@@ -105,17 +101,53 @@ export const contentSecurityPolicyMiddleware = (
105101 // https://github.com/w3c/webappsec-csp/issues/199.
106102 `default-src 'self'` ,
107103
108- ...( opts . reportViolations
109- ? [ `report-uri https://csp.withgoogle.com/csp/lit-dev` ]
110- : [ ] ) ,
104+ ...( opts . reportViolations ? [ `report-uri ${ CSP_REPORT_URI } ` ] : [ ] ) ,
105+ ] . join ( '; ' ) ;
106+
107+ // TODO(aomarks) Currently this worker CSP will take effect in Firefox and
108+ // Safari, but not Chrome. Chrome does not currently follow the CSP spec for
109+ // workers; instead workers inherit the CSP policy of their parent context.
110+ // This is being actively fixed (https://crbug.com/1253267), and once it ships
111+ // we can remove unsafe-eval and unpkg.com from the main CSP above.
112+ const playgroundWorkerCsp = [
113+ // unsafe-eval is needed because we use es-module-lexer to parse import
114+ // statements in modules. es-module-lexer needs unsafe-eval because:
115+ //
116+ // 1. It uses Web Assembly, which requires unsafe-eval until
117+ // wasm-unsafe-eval ships in all browsers:
118+ //
119+ // Spec: https://github.com/w3c/webappsec-csp/pull/293
120+ // Chrome: https://bugs.chromium.org/p/chromium/issues/detail?id=948834
121+ // Safari: https://bugs.webkit.org/show_bug.cgi?id=197759
122+ // Firefox: <not yet filed>
123+ //
124+ // 2. It uses eval() to parse JavaScript string literals
125+ // (https://github.com/guybedford/es-module-lexer/blob/91964da6b086dc5029091eeef481180a814ce24a/src/lexer.js#L32).
126+ // This is theoretically a safe use of eval because it's only used to
127+ // process strings that have already been lexed as single or double quote
128+ // strings. Trusted types could be used to isolate the exact eval() call.
129+ // It may also be a good idea to replace the eval call with a fast WASM
130+ // implementation of string unescaping.
131+ `script-src 'unsafe-eval'` ,
132+
133+ // Allow bare module specifiers to be fetched from unpkg. Note this does not
134+ // restrict the user from directly importing from arbitrary other URLs in
135+ // their import statements when using the Playground.
136+ `connect-src https://unpkg.com/` ,
137+
138+ // Disallow everything else.
139+ `default-src 'none'` ,
140+ ...( opts . reportViolations ? [ `report-uri ${ CSP_REPORT_URI } ` ] : [ ] ) ,
111141 ] . join ( '; ' ) ;
112142
113143 return async ( ctx , next ) => {
114144 await next ( ) ;
115145 if ( ctx . response . type === 'text/html' ) {
116146 // TODO(aomarks) Remove -Report-Only suffix when we are confident the
117147 // policy is working.
118- ctx . set ( 'Content-Security-Policy-Report-Only' , cspHeaderValue ) ;
148+ ctx . set ( 'Content-Security-Policy-Report-Only' , mainCsp ) ;
149+ } else if ( ctx . path . endsWith ( '/playground-typescript-worker.js' ) ) {
150+ ctx . set ( 'Content-Security-Policy-Report-Only' , playgroundWorkerCsp ) ;
119151 }
120152 } ;
121153} ;
0 commit comments