CSP content hashes (#1406)

* remove duplicate content-type header

* plumbing to compute CSP content hashes

* add logging of missing CSP hashes

* convert sub-class init protocol to static

* factor function

* lint
This commit is contained in:
Bruce Martin
2020-04-17 11:42:09 -07:00
committed by GitHub
parent d22300e3db
commit 55ef1448e8
9 changed files with 191 additions and 38 deletions
@@ -0,0 +1,50 @@
const cheerio = require("cheerio");
const crypto = require("crypto");
HtmlWebpackPlugin = require("html-webpack-plugin");
class CspHashPlugin {
constructor(opts) {
this.opts = { ...opts };
}
apply(compiler) {
compiler.hooks.compilation.tap("CspHashPlugin", (compilation) => {
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync(
"CspHashPlugin",
(data, cb) => {
const { filename } = this.opts;
if (filename) {
const $ = cheerio.load(data.html, { decodeEntities: false });
const results = {};
results["script-hashes"] = $("script:not([src])")
.map((i, elmt) => this.digest($(elmt).html()))
.get();
results["style-hashes"] = $("style:not([href])")
.map((i, elmt) => this.digest($(elmt).html()))
.get();
const json = JSON.stringify(results);
compilation.assets[filename] = {
source: () => json,
size: () => json.length,
};
}
// Tell webpack to move on
cb(null, data);
}
);
});
}
digest(str) {
const hash = crypto
.createHash("sha256")
.update(str, "utf8")
.digest("base64");
return "sha256-" + hash;
}
}
module.exports = CspHashPlugin;
@@ -5,6 +5,7 @@ const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackInlineSourcePlugin = require("html-webpack-inline-source-plugin");
const FaviconsWebpackPlugin = require("favicons-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const CspHashPlugin = require("./cspHashPlugin");
const src = path.resolve("src");
const fonts = path.resolve("src/fonts");
@@ -21,7 +22,7 @@ module.exports = {
entry: ["./src/index.js"],
output: {
path: path.resolve("build"),
publicPath
publicPath,
},
module: {
rules: [
@@ -29,7 +30,7 @@ module.exports = {
test: /\.js$/,
include: src,
loader: "babel-loader",
options: babelOptions
options: babelOptions,
},
{
test: /\.css$/,
@@ -41,12 +42,12 @@ module.exports = {
loader: "css-loader",
options: {
modules: {
localIdentName: "[name]__[local]___[hash:base64:5]"
localIdentName: "[name]__[local]___[hash:base64:5]",
},
importLoaders: 1
}
}
]
importLoaders: 1,
},
},
],
},
{
test: /index\.css$/,
@@ -56,24 +57,24 @@ module.exports = {
{
loader: "css-loader",
options: {
importLoaders: 1
}
}
]
importLoaders: 1,
},
},
],
},
{
test: /\.json$/,
include: [src, nodeModules],
loader: "json-loader",
exclude: /manifest.json$/
exclude: /manifest.json$/,
},
{
test: /\.(jpg|png|gif|eot|svg|ttf|woff|woff2|otf)$/i,
loader: "file-loader",
include: [nodeModules, fonts],
query: { name: "static/assets/[name]-[contenthash].[ext]" }
}
]
query: { name: "static/assets/[name]-[contenthash].[ext]" },
},
],
},
plugins: [
new HtmlWebpackPlugin({
@@ -91,13 +92,13 @@ module.exports = {
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true
}
minifyURLs: true,
},
}),
new CleanWebpackPlugin({
verbose: true,
protectWebpackAssets: false,
cleanAfterEveryBuildPatterns: ["main.js", "main.css"]
cleanAfterEveryBuildPatterns: ["main.js", "main.css"],
}),
new FaviconsWebpackPlugin({
logo: "./favicon.png",
@@ -110,15 +111,18 @@ module.exports = {
coast: false,
firefox: false,
windows: false,
yandex: false
}
}
yandex: false,
},
},
}),
new HtmlWebpackInlineSourcePlugin(HtmlWebpackPlugin),
new MiniCssExtractPlugin()
new MiniCssExtractPlugin(),
new CspHashPlugin({
filename: "csp-hashes.json",
}),
],
performance: {
maxEntrypointSize: 2000000,
maxAssetSize: 2000000
}
maxAssetSize: 2000000,
},
};