diff --git a/packages/lit-dev-server/src/middleware/content-security-policy-middleware.ts b/packages/lit-dev-server/src/middleware/content-security-policy-middleware.ts
index d56234883..1dd27bbb2 100644
--- a/packages/lit-dev-server/src/middleware/content-security-policy-middleware.ts
+++ b/packages/lit-dev-server/src/middleware/content-security-policy-middleware.ts
@@ -38,6 +38,12 @@ export interface ContentSecurityPolicyMiddlewareOptions {
*/
const CSP_REPORT_URI = 'https://csp.withgoogle.com/csp/lit-dev';
+/**
+ * TODO(aomarks) Generate this automatically. See
+ * https://github.com/lit/lit.dev/issues/531.
+ */
+const GOOGLE_ANALYTICS_INLINE_SCRIPT_HASH = `'sha256-bG+QS/Ob2lFyxJ7r7PCtj/a8YofLHFx4t55RzjR1znI='`;
+
/**
* Creates a Koa middleware that sets the lit.dev Content Security Policy (CSP)
* headers.
@@ -47,23 +53,40 @@ const CSP_REPORT_URI = 'https://csp.withgoogle.com/csp/lit-dev';
* https://www.w3.org/TR/CSP3/
* https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
* https://speakerdeck.com/lweichselbaum/csp-a-successful-mess-between-hardening-and-mitigation
+ * https://csp-evaluator.withgoogle.com/
*/
export const contentSecurityPolicyMiddleware = (
opts: ContentSecurityPolicyMiddlewareOptions
): Koa.Middleware => {
- const mainCsp = [
+ const makePolicy = (...directives: string[]) =>
+ [
+ ...directives,
+ // Prevent an injected tag from modifying relative URLs.
+ `base-uri 'none'`,
+ // Prevent form submissions.
+ `form-action 'none'`,
+ // Disallow other sites from iframing this site (e.g. clickjacking).
+ `frame-ancestors 'none'`,
+ ...(opts.reportViolations ? [`report-uri ${CSP_REPORT_URI}`] : []),
+ ].join('; ');
+
+ // Policy for the main HTML entrypoints (homepage, docs, playground, etc.)
+ const htmlCsp = makePolicy(
// TODO(aomarks) We should also enable trusted types, but that will require
// a policy in playground-elements for creating the worker, and a policy
// es-module-lexer for doing an eval (see next comment for more on that).
-
- // TODO(aomarks) Remove unsafe-eval when https://crbug.com/1253267 is fixed.
- // See comment below about playgroundWorkerCsp.
- //
- // In dev mode, data: scripts are required because @web/dev-server uses them
- // for automatic reloads.
- `script-src 'self' 'unsafe-eval' ${
- opts.inlineScriptHashes?.map((hash) => `'${hash}'`).join(' ') ?? ''
- } https://www.googletagmanager.com/gtag/js ${opts.devMode ? ` data:` : ''}`,
+ `script-src ${[
+ `'self'`,
+ // TODO(aomarks) Remove unsafe-eval when https://crbug.com/1253267 is fixed.
+ // See comment below about playgroundWorkerCsp.
+ `'unsafe-eval'`,
+ `https://www.googletagmanager.com/gtag/js`,
+ GOOGLE_ANALYTICS_INLINE_SCRIPT_HASH,
+ ...(opts.inlineScriptHashes?.map((hash) => `'${hash}'`) ?? []),
+ // In dev mode, data: scripts are required because @web/dev-server uses them
+ // for automatic reloads.
+ ...(opts.devMode ? [`data:`] : []),
+ ].join(' ')}`,
// TODO(aomarks) Remove unpkg.com when https://crbug.com/1253267 is fixed.
// See comment below about playgroundWorkerCsp.
@@ -94,22 +117,27 @@ export const contentSecurityPolicyMiddleware = (
// The ytimg.com domain is needed for embedded YouTube videos.
`img-src 'self' data: https://i.ytimg.com/`,
+ // Disallow any embeds, applets, etc. This would usually be covered by
+ // `default-src: 'none'`, but we can't set that for the reason explained
+ // below.
+ `object-src 'none'`,
+
// TODO(aomarks) This could be 'none' if we didn't use