diff --git a/package-lock.json b/package-lock.json index e1bac56a..f906e6b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,11 +5,10 @@ "requires": true, "packages": { "": { - "name": "squoosh", "version": "2.0.0", "license": "apache-2.0", "dependencies": { - "cheerio": "^1.0.0-rc.10", + "strict-csp": "^1.0.2", "wasm-feature-detect": "^1.2.11" }, "devDependencies": { @@ -288,6 +287,14 @@ "node": ">=6" } }, + "node_modules/@types/cheerio": { + "version": "0.22.30", + "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.30.tgz", + "integrity": "sha512-t7ZVArWZlq3dFa9Yt33qFBQIK4CQd1Q3UJp0V+UhP6vgLWLM6Qug7vZuRSGXg45zXeB1Fm5X2vmBkEX58LV2Tw==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", @@ -331,8 +338,7 @@ "node_modules/@types/node": { "version": "16.11.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.1.tgz", - "integrity": "sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA==", - "dev": true + "integrity": "sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA==" }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -8185,6 +8191,15 @@ "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", "dev": true }, + "node_modules/strict-csp": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/strict-csp/-/strict-csp-1.0.2.tgz", + "integrity": "sha512-YLnlJIGZQYRPRo39d/wNmF1CVkaikwCsIcLBWvVBWYAxQGFjigolOE2oPeqPMFt19NPM+Vts/z5nT15nRcKNUg==", + "dependencies": { + "@types/cheerio": "^0.22.23", + "cheerio": "^1.0.0-rc.5" + } + }, "node_modules/string-argv": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", @@ -9105,6 +9120,14 @@ } } }, + "@types/cheerio": { + "version": "0.22.30", + "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.30.tgz", + "integrity": "sha512-t7ZVArWZlq3dFa9Yt33qFBQIK4CQd1Q3UJp0V+UhP6vgLWLM6Qug7vZuRSGXg45zXeB1Fm5X2vmBkEX58LV2Tw==", + "requires": { + "@types/node": "*" + } + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", @@ -9148,8 +9171,7 @@ "@types/node": { "version": "16.11.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.1.tgz", - "integrity": "sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA==", - "dev": true + "integrity": "sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA==" }, "@types/parse-json": { "version": "4.0.0", @@ -15656,6 +15678,15 @@ "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", "dev": true }, + "strict-csp": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/strict-csp/-/strict-csp-1.0.2.tgz", + "integrity": "sha512-YLnlJIGZQYRPRo39d/wNmF1CVkaikwCsIcLBWvVBWYAxQGFjigolOE2oPeqPMFt19NPM+Vts/z5nT15nRcKNUg==", + "requires": { + "@types/cheerio": "^0.22.23", + "cheerio": "^1.0.0-rc.5" + } + }, "string-argv": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", diff --git a/package.json b/package.json index e02c8157..b7a1e425 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "*.rs": "rustfmt" }, "dependencies": { - "cheerio": "^1.0.0-rc.10", + "strict-csp": "^1.0.2", "wasm-feature-detect": "^1.2.11" } } diff --git a/src/static-build/lib-csp.ts b/src/static-build/lib-csp.ts deleted file mode 100644 index 685c476d..00000000 --- a/src/static-build/lib-csp.ts +++ /dev/null @@ -1,192 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import * as crypto from 'crypto'; -import * as cheerio from 'cheerio'; - -/** Module for enabling a hash-based strict Content Security Policy. */ -export class StrictCsp { - private static readonly HASH_FUNCTION = 'sha256'; - private static readonly INLINE_SCRIPT_SELECTOR = 'script:not([src])'; - private static readonly SOURCED_SCRIPT_SELECTOR = 'script[src]'; - private $: any; - - constructor(html: string) { - this.$ = cheerio.load(html, { - decodeEntities: false, - xmlMode: false, - }); - } - - serializeDom(): string { - return this.$.html(); - } - - /** - * Returns a strict Content Security Policy for mitigating XSS. - * For more details read csp.withgoogle.com. - * If you modify this CSP, make sure it has not become trivially bypassable by - * checking the policy using csp-evaluator.withgoogle.com. - * - * @param hashes A list of sha-256 hashes of trusted inline scripts. - * @param enableTrustedTypes If Trusted Types should be enabled for scripts. - * @param enableBrowserFallbacks If fallbacks for older browsers should be - * added. This is will not weaken the policy as modern browsers will ignore - * the fallbacks. - */ - static getStrictCsp( - hashes?: string[], - enableTrustedTypes?: boolean, - enableBrowserFallbacks?: boolean, - ): string { - hashes = hashes || []; - let strictCspTemplate = { - // 'strict-dynamic' allows hashed scripts to create new scripts. - 'script-src': [`'strict-dynamic'`, ...hashes], - // Restricts `object-src` to disable dangerous plugins like Flash. - 'object-src': [`'none'`], - // Restricts `base-uri` to block the injection of `` tags. This - // prevents attackers from changing the locations of scripts loaded from - // relative URLs. - 'base-uri': [`'self'`], - }; - - // Adds fallbacks for browsers not compatible to CSP3 and CSP2. - // These fallbacks are ignored by modern browsers in presence of hashes, - // and 'strict-dynamic'. - if (enableBrowserFallbacks) { - // Fallback for Safari. All modern browsers supporting strict-dynamic will - // ignore the 'https:' fallback. - strictCspTemplate['script-src'].push('https:'); - // 'unsafe-inline' is only ignored in presence of a hash or nonce. - if (hashes.length > 0) { - strictCspTemplate['script-src'].push(`'unsafe-inline'`); - } - } - - // If enabled, dangerous DOM sinks will only accept typed objects instead of - // strings. - if (enableTrustedTypes) { - strictCspTemplate = { - ...strictCspTemplate, - ...{ 'require-trusted-types-for': [`'script'`] }, - }; - } - - return Object.entries(strictCspTemplate) - .map(([directive, values]) => { - return `${directive} ${values.join(' ')};`; - }) - .join(''); - } - - /** - * Enables a CSP via a meta tag at the beginning of the document. - * Warning: It's recommended to set CSP as HTTP response header instead of - * using a meta tag. Injections before the meta tag will not be covered by CSP - * and meta tags don't support CSP in report-only mode. - * - * @param csp A Content Security Policy string. - */ - addMetaTag(csp: string): void { - let metaTag = this.$('meta[http-equiv="Content-Security-Policy"]'); - if (!metaTag.length) { - metaTag = cheerio.load('')( - 'meta', - ); - metaTag.prependTo(this.$('head')); - } - metaTag.attr('content', csp); - } - - /** - * Replaces all sourced scripts with a single inline script that can be hashed - */ - refactorSourcedScriptsForHashBasedCsp(): void { - const srcList = this.$(StrictCsp.SOURCED_SCRIPT_SELECTOR) - .map((i: any, script: any) => { - const src = this.$(script).attr('src'); - this.$(script).remove(); - return src; - }) - .filter((src: any) => src !== null) - .get(); - - const loaderScript = StrictCsp.createLoaderScript(srcList); - if (!loaderScript) { - return; - } - - // const hash = StrictCsp.hashInlineScript(loaderScript); - // const comment = cheerio.load(``).root(); - // comment.appendTo(this.$('body')); - const newScript = cheerio.load('