@@ -38,6 +38,12 @@ export interface ContentSecurityPolicyMiddlewareOptions {
3838 */
3939const CSP_REPORT_URI = 'https://csp.withgoogle.com/csp/lit-dev' ;
4040
41+ /**
42+ * TODO(aomarks) Generate this automatically. See
43+ * https://github.com/lit/lit.dev/issues/531.
44+ */
45+ const GOOGLE_ANALYTICS_INLINE_SCRIPT_HASH = `'sha256-bG+QS/Ob2lFyxJ7r7PCtj/a8YofLHFx4t55RzjR1znI='` ;
46+
4147/**
4248 * Creates a Koa middleware that sets the lit.dev Content Security Policy (CSP)
4349 * headers.
@@ -47,23 +53,40 @@ const CSP_REPORT_URI = 'https://csp.withgoogle.com/csp/lit-dev';
4753 * https://www.w3.org/TR/CSP3/
4854 * https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
4955 * https://speakerdeck.com/lweichselbaum/csp-a-successful-mess-between-hardening-and-mitigation
56+ * https://csp-evaluator.withgoogle.com/
5057 */
5158export const contentSecurityPolicyMiddleware = (
5259 opts : ContentSecurityPolicyMiddlewareOptions
5360) : Koa . Middleware => {
54- const mainCsp = [
61+ const makePolicy = ( ...directives : string [ ] ) =>
62+ [
63+ ...directives ,
64+ // Prevent an injected <base> tag from modifying relative URLs.
65+ `base-uri 'none'` ,
66+ // Prevent form submissions.
67+ `form-action 'none'` ,
68+ // Disallow other sites from iframing this site (e.g. clickjacking).
69+ `frame-ancestors 'none'` ,
70+ ...( opts . reportViolations ? [ `report-uri ${ CSP_REPORT_URI } ` ] : [ ] ) ,
71+ ] . join ( '; ' ) ;
72+
73+ // Policy for the main HTML entrypoints (homepage, docs, playground, etc.)
74+ const htmlCsp = makePolicy (
5575 // TODO(aomarks) We should also enable trusted types, but that will require
5676 // a policy in playground-elements for creating the worker, and a policy
5777 // es-module-lexer for doing an eval (see next comment for more on that).
58-
59- // TODO(aomarks) Remove unsafe-eval when https://crbug.com/1253267 is fixed.
60- // See comment below about playgroundWorkerCsp.
61- //
62- // In dev mode, data: scripts are required because @web/dev-server uses them
63- // for automatic reloads.
64- `script-src 'self' 'unsafe-eval' ${
65- opts . inlineScriptHashes ?. map ( ( hash ) => `'${ hash } '` ) . join ( ' ' ) ?? ''
66- } https://www.googletagmanager.com/gtag/js ${ opts . devMode ? ` data:` : '' } `,
78+ `script-src ${ [
79+ `'self'` ,
80+ // TODO(aomarks) Remove unsafe-eval when https://crbug.com/1253267 is fixed.
81+ // See comment below about playgroundWorkerCsp.
82+ `'unsafe-eval'` ,
83+ `https://www.googletagmanager.com/gtag/js` ,
84+ GOOGLE_ANALYTICS_INLINE_SCRIPT_HASH ,
85+ ...( opts . inlineScriptHashes ?. map ( ( hash ) => `'${ hash } '` ) ?? [ ] ) ,
86+ // In dev mode, data: scripts are required because @web/dev-server uses them
87+ // for automatic reloads.
88+ ...( opts . devMode ? [ `data:` ] : [ ] ) ,
89+ ] . join ( ' ' ) } `,
6790
6891 // TODO(aomarks) Remove unpkg.com when https://crbug.com/1253267 is fixed.
6992 // See comment below about playgroundWorkerCsp.
@@ -94,22 +117,27 @@ export const contentSecurityPolicyMiddleware = (
94117 // The ytimg.com domain is needed for embedded YouTube videos.
95118 `img-src 'self' data: https://i.ytimg.com/` ,
96119
120+ // Disallow any embeds, applets, etc. This would usually be covered by
121+ // `default-src: 'none'`, but we can't set that for the reason explained
122+ // below.
123+ `object-src 'none'` ,
124+
97125 // TODO(aomarks) This could be 'none' if we didn't use <svg><use> elements,
98126 // because Firefox does not follow the img-src directive for them, so there
99127 // is no other directive to use. See
100128 // https://bugzilla.mozilla.org/show_bug.cgi?id=1303364#c4 and
101129 // https://github.com/w3c/webappsec-csp/issues/199.
102- `default-src 'self'` ,
103-
104- ...( opts . reportViolations ? [ `report-uri ${ CSP_REPORT_URI } ` ] : [ ] ) ,
105- ] . join ( '; ' ) ;
130+ `default-src 'self'`
131+ ) ;
106132
133+ // Policy for the playground-elements web worker script.
134+ //
107135 // TODO(aomarks) Currently this worker CSP will take effect in Firefox and
108136 // Safari, but not Chrome. Chrome does not currently follow the CSP spec for
109137 // workers; instead workers inherit the CSP policy of their parent context.
110138 // This is being actively fixed (https://crbug.com/1253267), and once it ships
111139 // we can remove unsafe-eval and unpkg.com from the main CSP above.
112- const playgroundWorkerCsp = [
140+ const playgroundWorkerCsp = makePolicy (
113141 // unsafe-eval is needed because we use es-module-lexer to parse import
114142 // statements in modules. es-module-lexer needs unsafe-eval because:
115143 //
@@ -136,18 +164,30 @@ export const contentSecurityPolicyMiddleware = (
136164 `connect-src https://unpkg.com/` ,
137165
138166 // Disallow everything else.
139- `default-src 'none'` ,
140- ...( opts . reportViolations ? [ `report-uri ${ CSP_REPORT_URI } ` ] : [ ] ) ,
141- ] . join ( '; ' ) ;
167+ `default-src 'none'`
168+ ) ;
169+
170+ // For all other responses, set the strictest possible CSP, just in case a
171+ // response that shouldn't normally allow any code execution actually does.
172+ //
173+ // See https://github.com/w3c/webappsec/issues/520#issuecomment-488516726 and
174+ // https://github.com/webhintio/hint/issues/3403#issue-528402128 for
175+ // discussion of why this is a good practice.
176+ const strictFallbackCsp = makePolicy ( `default-src 'none'` ) ;
142177
143178 return async ( ctx , next ) => {
144179 await next ( ) ;
180+
181+ let policy : string ;
145182 if ( ctx . response . type === 'text/html' ) {
146- // TODO(aomarks) Remove -Report-Only suffix when we are confident the
147- // policy is working.
148- ctx . set ( 'Content-Security-Policy-Report-Only' , mainCsp ) ;
183+ policy = htmlCsp ;
149184 } else if ( ctx . path . endsWith ( '/playground-typescript-worker.js' ) ) {
150- ctx . set ( 'Content-Security-Policy-Report-Only' , playgroundWorkerCsp ) ;
185+ policy = playgroundWorkerCsp ;
186+ } else {
187+ policy = strictFallbackCsp ;
151188 }
189+ // TODO(aomarks) Remove -Report-Only suffix when we are confident the
190+ // policy is working.
191+ ctx . set ( 'Content-Security-Policy-Report-Only' , policy ) ;
152192 } ;
153193} ;
0 commit comments