webpack-subresource-integrity
Advanced tools
Comparing version 0.6.0 to 0.7.0
413
index.js
@@ -5,2 +5,8 @@ var crypto = require('crypto'); | ||
// https://www.w3.org/TR/2016/REC-SRI-20160623/#cryptographic-hash-functions | ||
var standardHashFuncNames = ['sha256', 'sha384', 'sha512']; | ||
// https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes | ||
var standardCrossoriginOptions = ['anonymous', 'use-credentials']; | ||
function makePlaceholder(id) { | ||
@@ -12,4 +18,4 @@ return '*-*-*-CHUNK-SRI-HASH-' + id + '-*-*-*'; | ||
chunk.chunks.forEach(function forEachChunk(depChunk) { | ||
if (!allDepChunkIds.includes(depChunk.id)) { | ||
allDepChunkIds.push(depChunk.id); | ||
if (!allDepChunkIds[depChunk.id]) { | ||
allDepChunkIds[depChunk.id] = true; | ||
} | ||
@@ -20,32 +26,2 @@ findDepChunks(depChunk, allDepChunkIds); | ||
/* Given a public URL path to an asset, as generated by | ||
* HtmlWebpackPlugin for use as a `<script src>` or `<link href`> URL | ||
* in `index.html`, return the path in the filesystem relative to the | ||
* webpack output directory, suitable as a key into | ||
* `compilation.assets`. | ||
*/ | ||
function hwpSrcRelativeOutputPath(compiler, htmlWebpackPlugin, src) { | ||
var webpackOutputPath = compiler.options.output.path; | ||
// For publicPath we need to fall back to the empty string like | ||
// webpack does (see e.g. webpack/lib/MainTemplate.js) | ||
var webpackPublicPath = compiler.options.output.publicPath || ''; | ||
// Strip cache-busting hash query possibly added by HtmlWebpackPlugin | ||
var srcWithoutHash = src.replace(/\?[a-zA-Z0-9]+$/, ''); | ||
// Determine source path relative to webpack public path | ||
var srcPathRelativeToPublic = path.relative(webpackPublicPath, srcWithoutHash); | ||
// Using this source path, determine full output path name in | ||
// filesystem | ||
var hwpOutputPath = path.dirname(htmlWebpackPlugin.options.filename); | ||
var outputAssetPath = path.resolve(webpackOutputPath, | ||
hwpOutputPath, | ||
srcPathRelativeToPublic); | ||
// Return the output path name relative to webpack output directory | ||
return path.relative(webpackOutputPath, outputAssetPath); | ||
} | ||
function WebIntegrityJsonpMainTemplatePlugin() {} | ||
@@ -72,3 +48,3 @@ | ||
if (chunk.chunks.length > 0) { | ||
var allDepChunkIds = []; | ||
var allDepChunkIds = {}; | ||
findDepChunks(chunk, allDepChunkIds); | ||
@@ -80,3 +56,3 @@ | ||
this.indent( | ||
allDepChunkIds.map(function mapChunkId(chunkId) { | ||
Object.keys(allDepChunkIds).map(function mapChunkId(chunkId) { | ||
return chunkId + ':"' + makePlaceholder(chunkId) + '"'; | ||
@@ -92,158 +68,281 @@ }).join(',\n') | ||
function SubresourceIntegrityPlugin(algorithms) { | ||
if (typeof algorithms === 'string') { | ||
this.algorithms = [ algorithms ]; | ||
} else if (!Array.isArray(algorithms)) { | ||
throw new Error('Expected an array of strings or a string'); | ||
} else if (algorithms.length === 0) { | ||
throw new Error('Algorithms array must not be empty'); | ||
function SubresourceIntegrityPlugin(options) { | ||
var useOptions; | ||
if (typeof options === 'string') { | ||
useOptions = { | ||
hashFuncNames: [options], | ||
deprecatedOptions: true | ||
}; | ||
} else if (Array.isArray(options)) { | ||
useOptions = { | ||
hashFuncNames: options, | ||
deprecatedOptions: true | ||
}; | ||
} else if (options === null || typeof options === 'undefined') { | ||
useOptions = {}; | ||
} else if (typeof options === 'object') { | ||
useOptions = options; | ||
} else { | ||
this.algorithms = algorithms; | ||
throw new Error('webpack-subresource-integrity: argument must be an object'); | ||
} | ||
this.options = { | ||
enabled: true, | ||
crossorigin: 'anonymous' | ||
}; | ||
for (var key in useOptions) { | ||
if (useOptions.hasOwnProperty(key)) { | ||
this.options[key] = useOptions[key]; | ||
} | ||
} | ||
this.emittedWarnings = {}; | ||
} | ||
SubresourceIntegrityPlugin.prototype.emitMessage = function emitMessage(messages, message) { | ||
messages.push(new Error('webpack-subresource-integrity: ' + message)); | ||
}; | ||
SubresourceIntegrityPlugin.prototype.warnOnce = function warn(compilation, message) { | ||
if (!this.emittedWarnings[message]) { | ||
this.emittedWarnings[message] = true; | ||
this.emitMessage(compilation.warnings, message); | ||
} | ||
}; | ||
SubresourceIntegrityPlugin.prototype.error = function error(compilation, message) { | ||
this.emitMessage(compilation.errors, message); | ||
}; | ||
SubresourceIntegrityPlugin.prototype.validateOptions = function validateOptions(compilation) { | ||
if (this.options.deprecatedOptions) { | ||
this.warnOnce( | ||
compilation, | ||
'Passing a string or array to the plugin constructor is deprecated. ' + | ||
'Support will be removed in webpack-subresource-integrity 1.0.0. ' + | ||
'Please update your code. ' + | ||
'See https://github.com/waysact/webpack-subresource-integrity/issues/18 for more information.'); | ||
} | ||
if (!Array.isArray(this.options.hashFuncNames)) { | ||
this.error( | ||
compilation, | ||
'options.hashFuncNames must be an array of hash function names, ' + | ||
'instead got \'' + this.options.hashFuncNames + '\'.'); | ||
this.options.enabled = false; | ||
} else { | ||
var foundStandardHashFunc = false; | ||
for (var i = 0; i < this.options.hashFuncNames.length; i++) { | ||
var hashFuncName = this.options.hashFuncNames[i]; | ||
if (typeof hashFuncName !== 'string' && | ||
!(hashFuncName instanceof String)) { | ||
this.error( | ||
compilation, | ||
'options.hashFuncNames must be an array of hash function names, ' + | ||
'but contained ' + hashFuncName + '.'); | ||
this.options.enabled = false; | ||
return; | ||
} | ||
try { | ||
crypto.createHash(hashFuncName); | ||
} catch (error) { | ||
this.error( | ||
compilation, | ||
'Cannot use hash function \'' + hashFuncName + '\': ' + | ||
error.message); | ||
this.options.enabled = false; | ||
return; | ||
} | ||
if (standardHashFuncNames.indexOf(hashFuncName) >= 0) { | ||
foundStandardHashFunc = true; | ||
} | ||
} | ||
if (!foundStandardHashFunc) { | ||
this.warnOnce( | ||
compilation, | ||
'It is recommended that at least one hash function is part of the set ' + | ||
'for which support is mandated by the specification. ' + | ||
'These are: ' + standardHashFuncNames.join(', ') + '. ' + | ||
'See http://www.w3.org/TR/SRI/#cryptographic-hash-functions for more information.'); | ||
} | ||
} | ||
if (typeof this.options.crossorigin !== 'string' && | ||
!(this.options.crossorigin instanceof String)) { | ||
this.error( | ||
compilation, | ||
'options.crossorigin must be a string.'); | ||
this.options.enabled = false; | ||
return; | ||
} | ||
if (standardCrossoriginOptions.indexOf(this.options.crossorigin) < 0) { | ||
this.warnOnce( | ||
compilation, | ||
'You\'ve specified a value for the crossorigin option that is not part of the set of standard values. ' + | ||
'These are: ' + standardCrossoriginOptions.join(', ') + '. ' + | ||
'See https://www.w3.org/TR/SRI/#cross-origin-data-leakage for more information.'); | ||
} | ||
}; | ||
/* Given a public URL path to an asset, as generated by | ||
* HtmlWebpackPlugin for use as a `<script src>` or `<link href`> URL | ||
* in `index.html`, return the path to the asset, suitable as a key | ||
* into `compilation.assets`. | ||
*/ | ||
SubresourceIntegrityPlugin.prototype.hwpAssetPath = function hwpAssetPath(src) { | ||
return path.relative(this.hwpPublicPath, src.replace(/\?[a-zA-Z0-9]+$/, '')); | ||
}; | ||
SubresourceIntegrityPlugin.prototype.apply = function apply(compiler) { | ||
var algorithms = this.algorithms; | ||
var self = this; | ||
function computeIntegrity(source) { | ||
return algorithms.map(function mapAlgo(algo) { | ||
var hash = crypto.createHash(algo).update(source, 'utf8').digest('base64'); | ||
return algo + '-' + hash; | ||
return self.options.hashFuncNames.map(function mapHashFuncName(hashFuncName) { | ||
var hash = crypto.createHash(hashFuncName).update(source, 'utf8').digest('base64'); | ||
return hashFuncName + '-' + hash; | ||
}).join(' '); | ||
} | ||
compiler.plugin('compilation', function compilationPlugin(compilation) { | ||
/* | ||
* Double plug-in registration in order to push our | ||
* plugin to the end of the plugin stack. | ||
*/ | ||
compiler.plugin('this-compilation', function thisCompilationPlugin(thisCompilation) { | ||
thisCompilation.mainTemplate.apply(new WebIntegrityJsonpMainTemplatePlugin()); | ||
}); | ||
compiler.plugin('after-plugins', function afterPlugins() { | ||
compiler.plugin('this-compilation', function thisCompilation(compilation) { | ||
self.validateOptions(compilation); | ||
/* | ||
* Calculate SRI values for each chunk and replace the magic | ||
* placeholders by the actual values. | ||
*/ | ||
compilation.plugin('optimize-assets', function optimizeAssetsPlugin(assets, callback) { | ||
var hashByChunkId = {}; | ||
var visitedByChunkId = {}; | ||
function processChunkRecursive(chunk) { | ||
var depChunkIds = []; | ||
if (!self.options.enabled) { | ||
return; | ||
} | ||
if (visitedByChunkId[chunk.id]) { | ||
return []; | ||
} | ||
visitedByChunkId[chunk.id] = true; | ||
compilation.mainTemplate.apply(new WebIntegrityJsonpMainTemplatePlugin()); | ||
chunk.chunks.forEach(function mapChunk(depChunk) { | ||
depChunkIds = depChunkIds.concat(processChunkRecursive(depChunk)); | ||
}); | ||
/* | ||
* Calculate SRI values for each chunk and replace the magic | ||
* placeholders by the actual values. | ||
*/ | ||
compilation.plugin('after-optimize-assets', function optimizeAssetsPlugin(assets) { | ||
var hashByChunkId = {}; | ||
var visitedByChunkId = {}; | ||
function processChunkRecursive(chunk) { | ||
var depChunkIds = []; | ||
if (chunk.files.length > 0) { | ||
var chunkFile = chunk.files[0]; | ||
if (visitedByChunkId[chunk.id]) { | ||
return []; | ||
} | ||
visitedByChunkId[chunk.id] = true; | ||
var oldSource = assets[chunkFile].source(); | ||
var newAsset = new ReplaceSource(assets[chunkFile]); | ||
chunk.chunks.forEach(function mapChunk(depChunk) { | ||
depChunkIds = depChunkIds.concat(processChunkRecursive(depChunk)); | ||
}); | ||
depChunkIds.forEach(function forEachChunk(depChunkId) { | ||
var magicMarker = makePlaceholder(depChunkId); | ||
var magicMarkerPos = oldSource.indexOf(magicMarker); | ||
if (magicMarkerPos >= 0) { | ||
newAsset.replace( | ||
magicMarkerPos, | ||
magicMarkerPos + magicMarker.length - 1, | ||
hashByChunkId[depChunkId]); | ||
if (chunk.files.length > 0) { | ||
var chunkFile = chunk.files[0]; | ||
var oldSource = assets[chunkFile].source(); | ||
if (oldSource.indexOf('webpackHotUpdate') >= 0) { | ||
self.warnOnce( | ||
compilation, | ||
'Chunks loaded by HMR are unprotected. ' + | ||
'Consider disabling webpack-subresource-integrity in development mode.' | ||
); | ||
} | ||
}); | ||
assets[chunkFile] = newAsset; | ||
var newAsset = new ReplaceSource(assets[chunkFile]); | ||
var newSource = newAsset.source(); | ||
hashByChunkId[chunk.id] = newAsset.integrity = computeIntegrity(newSource); | ||
depChunkIds.forEach(function forEachChunk(depChunkId) { | ||
var magicMarker = makePlaceholder(depChunkId); | ||
var magicMarkerPos = oldSource.indexOf(magicMarker); | ||
if (magicMarkerPos >= 0) { | ||
newAsset.replace( | ||
magicMarkerPos, | ||
magicMarkerPos + magicMarker.length - 1, | ||
hashByChunkId[depChunkId]); | ||
} | ||
}); | ||
assets[chunkFile] = newAsset; | ||
var newSource = newAsset.source(); | ||
hashByChunkId[chunk.id] = newAsset.integrity = computeIntegrity(newSource); | ||
} | ||
return [ chunk.id ].concat(depChunkIds); | ||
} | ||
return [ chunk.id ].concat(depChunkIds); | ||
} | ||
compilation.chunks.forEach(function forEachChunk(chunk) { | ||
// chunk.entry was removed in Webpack 2. Use hasRuntime() for this check instead (if it exists) | ||
if (('hasRuntime' in chunk) ? chunk.hasRuntime() : chunk.entry) { | ||
processChunkRecursive(chunk); | ||
compilation.chunks.forEach(function forEachChunk(chunk) { | ||
// chunk.entry was removed in Webpack 2. Use hasRuntime() for this check instead (if it exists) | ||
if (('hasRuntime' in chunk) ? chunk.hasRuntime() : chunk.entry) { | ||
processChunkRecursive(chunk); | ||
} | ||
}); | ||
for (var key in assets) { | ||
if (assets.hasOwnProperty(key)) { | ||
var asset = assets[key]; | ||
if (!asset.integrity) { | ||
asset.integrity = computeIntegrity(asset.source()); | ||
} | ||
} | ||
} | ||
}); | ||
for (var key in assets) { | ||
if (assets.hasOwnProperty(key)) { | ||
var asset = assets[key]; | ||
if (!asset.integrity) { | ||
asset.integrity = computeIntegrity(asset.source()); | ||
} | ||
} | ||
function getTagSrc(tag) { | ||
// Get asset path - src from scripts and href from links | ||
return tag.attributes.href || tag.attributes.src; | ||
} | ||
callback(); | ||
}); | ||
function filterTag(tag) { | ||
// Process only script and link tags with a url | ||
return (tag.tagName === 'script' || tag.tagName === 'link') && getTagSrc(tag); | ||
} | ||
function getTagSrc(tag) { | ||
// Get asset path - src from scripts and href from links | ||
return tag.attributes.href || tag.attributes.src; | ||
} | ||
function getIntegrityChecksumForAsset(src) { | ||
var asset = compilation.assets[src]; | ||
return asset && asset.integrity; | ||
} | ||
function filterTag(tag) { | ||
// Process only script and link tags with a url | ||
return (tag.tagName === 'script' || tag.tagName === 'link') && getTagSrc(tag); | ||
} | ||
function alterAssetTags(pluginArgs, callback) { | ||
/* html-webpack-plugin has added an event so we can pre-process the html tags before they | ||
inject them. This does the work. | ||
*/ | ||
function processTag(tag) { | ||
var src = self.hwpAssetPath(getTagSrc(tag)); | ||
var checksum = getIntegrityChecksumForAsset(src); | ||
if (!checksum) { | ||
self.warnOnce( | ||
compilation, | ||
'Cannot determine hash for asset \'' + | ||
src + '\', the resource will be unprotected.'); | ||
return; | ||
} | ||
// Add integrity check sums | ||
tag.attributes.integrity = checksum; | ||
tag.attributes.crossorigin = self.options.crossorigin; | ||
} | ||
function getIntegrityChecksumForAsset(src) { | ||
var asset = compilation.assets[src]; | ||
return asset && asset.integrity; | ||
} | ||
pluginArgs.head.filter(filterTag).forEach(processTag); | ||
pluginArgs.body.filter(filterTag).forEach(processTag); | ||
callback(null, pluginArgs); | ||
} | ||
function alterAssetTags(pluginArgs, callback) { | ||
/* html-webpack-plugin has added an event so we can pre-process the html tags before they | ||
inject them. This does the work. | ||
*/ | ||
function processTag(tag) { | ||
var src = hwpSrcRelativeOutputPath(compiler, pluginArgs.plugin, getTagSrc(tag)); | ||
var checksum = getIntegrityChecksumForAsset(src); | ||
if (!checksum) { | ||
compilation.warnings.push(new Error( | ||
"webpack-subresource-integrity: cannot determine hash for asset '" + | ||
src + "', the resource will be unprotected.")); | ||
return; | ||
} | ||
// Add integrity check sums | ||
tag.attributes.integrity = checksum; | ||
tag.attributes.crossorigin = 'anonymous'; | ||
/* Add jsIntegrity and cssIntegrity properties to pluginArgs, to | ||
* go along with js and css properties. These are later | ||
* accessible on `htmlWebpackPlugin.files`. | ||
*/ | ||
function beforeHtmlGeneration(pluginArgs, callback) { | ||
self.hwpPublicPath = pluginArgs.assets.publicPath; | ||
['js', 'css'].forEach(function addIntegrity(fileType) { | ||
pluginArgs.assets[fileType + 'Integrity'] = | ||
pluginArgs.assets[fileType].map(function assetIntegrity(filePath) { | ||
var src = self.hwpAssetPath(filePath); | ||
return compilation.assets[src].integrity; | ||
}); | ||
}); | ||
pluginArgs.plugin.options.sriCrossOrigin = self.options.crossorigin; | ||
callback(null, pluginArgs); | ||
} | ||
pluginArgs.head.filter(filterTag).forEach(processTag); | ||
pluginArgs.body.filter(filterTag).forEach(processTag); | ||
callback(null, pluginArgs); | ||
} | ||
/* Add jsIntegrity and cssIntegrity properties to pluginArgs, to | ||
* go along with js and css properties. These are later | ||
* accessible on `htmlWebpackPlugin.files`. | ||
*/ | ||
function beforeHtmlGeneration(pluginArgs, callback) { | ||
['js', 'css'].forEach(function addIntegrity(fileType) { | ||
pluginArgs.assets[fileType + 'Integrity'] = | ||
pluginArgs.assets[fileType].map(function assetIntegrity(filePath) { | ||
var src = hwpSrcRelativeOutputPath(compilation.compiler, | ||
pluginArgs.plugin, | ||
filePath); | ||
return compilation.assets[src].integrity; | ||
}); | ||
}); | ||
callback(null, pluginArgs); | ||
} | ||
/* | ||
* html-webpack support: | ||
* Modify the asset tags before webpack injects them for anything with an integrity value. | ||
*/ | ||
compilation.plugin('html-webpack-plugin-alter-asset-tags', alterAssetTags); | ||
compilation.plugin('html-webpack-plugin-before-html-generation', beforeHtmlGeneration); | ||
/* | ||
* html-webpack support: | ||
* Modify the asset tags before webpack injects them for anything with an integrity value. | ||
*/ | ||
compilation.plugin('html-webpack-plugin-alter-asset-tags', alterAssetTags); | ||
compilation.plugin('html-webpack-plugin-before-html-generation', beforeHtmlGeneration); | ||
}); | ||
}); | ||
@@ -250,0 +349,0 @@ }; |
{ | ||
"name": "webpack-subresource-integrity", | ||
"version": "0.6.0", | ||
"version": "0.7.0", | ||
"description": "Webpack plugin for ensuring subresource integrity (SRI)", | ||
@@ -5,0 +5,0 @@ "main": "index", |
186
README.md
# webpack-subresource-integrity | ||
[![Build Status](https://travis-ci.org/waysact/webpack-subresource-integrity.svg?branch=master)](https://travis-ci.org/waysact/webpack-subresource-integrity) | ||
[![npm version](https://badge.fury.io/js/webpack-subresource-integrity.svg)](https://badge.fury.io/js/webpack-subresource-integrity) [![Build Status](https://travis-ci.org/waysact/webpack-subresource-integrity.svg?branch=master)](https://travis-ci.org/waysact/webpack-subresource-integrity) [![Dependency Status](https://david-dm.org/waysact/webpack-subresource-integrity.svg)](https://david-dm.org/waysact/webpack-subresource-integrity) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/waysact/webpack-subresource-integrity/master/LICENSE) | ||
A Webpack plugin for ensuring | ||
[subresource integrity](http://www.w3.org/TR/SRI/) on | ||
[supported browsers](http://caniuse.com/#feat=subresource-integrity). | ||
Webpack plugin for enabling Subresource Integrity. | ||
Integrity is ensured automatically for lazy-loaded chunks (loaded via | ||
`require.ensure`). | ||
[Subresource Integrity](http://www.w3.org/TR/SRI/) (SRI) is a security | ||
feature that enables browsers to verify that files they fetch (for | ||
example, from a CDN) are delivered without unexpected | ||
manipulation. | ||
It's your responsibility to include the `integrity` attribute in the | ||
HTML for top-level chunks. Obviously, SRI for lazy-loaded chunks is | ||
pointless unless integrity of the top-level chunks is ensured as well. | ||
## Features | ||
[html-webpack-plugin](https://github.com/ampedandwired/html-webpack-plugin) | ||
users can get the `integrity` attribute set automatically, see below. | ||
- Integration with [html-webpack-plugin](https://github.com/ampedandwired/html-webpack-plugin) | ||
- Support for code-splitting (integrity for lazy-loaded chunks) | ||
## Usage | ||
## Installation | ||
### Installing the Plugin | ||
```shell | ||
npm install webpack-subresource-integrity --save-dev | ||
``` | ||
$ npm install webpack-subresource-integrity --save-dev | ||
### Webpack Configuration Example | ||
Pass an array of | ||
[hash algorithms](http://www.w3.org/TR/SRI/#cryptographic-hash-functions) | ||
to the plugin constructor: | ||
```javascript | ||
import SriPlugin from 'webpack-subresource-integrity'; | ||
import SriPlugin from 'webpack-subresource-integrity'; | ||
const compiler = webpack({ | ||
plugins: [ | ||
new SriPlugin({ | ||
hashFuncNames: ['sha256', 'sha384'], | ||
enabled: process.env.NODE_ENV === 'production', | ||
crossorigin: 'anonymous', | ||
}), | ||
], | ||
}); | ||
``` | ||
const compiler = webpack({ | ||
plugins: [ | ||
// ... | ||
new SriPlugin(['sha256', 'sha384']), | ||
], | ||
}); | ||
### Setting the `integrity` attribute for top-level assets | ||
### Accessing the `integrity` Value for Top-level Assets | ||
For the plugin to take effect it is **essential** that you set the | ||
`integrity` attribute for top-level assets (i.e. assets loaded by your | ||
HTML pages.) | ||
The correct value for the `integrity` attribute can be retrieved from | ||
the `integrity` property of webpack assets. However, that property is | ||
not copied over by webpack's `stats` module so you'll have to access | ||
the "original" asset on the `compilation` object. Something like | ||
this: | ||
#### With HtmlWebpackPlugin | ||
compiler.plugin("done", stats => { | ||
var integrity = stats.compilation.assets[stats.toJson().assetsByChunkName.main].integrity; | ||
}); | ||
When html-webpack-plugin is injecting assets into the template (the | ||
default), the `integrity` attribute will be set automatically. There | ||
is nothing else to be done. | ||
Use that value to generate the `integrity` attribute for tags such as | ||
`<script>` and `<link>`. Note that you are also | ||
[required to set the `crossorigin` attribute](https://www.w3.org/TR/SRI/#cross-origin-data-leakage). | ||
#### With HtmlWebpackPlugin({ inject: false }) | ||
#### `html-webpack-plugin` Integration | ||
When you use html-webpack-plugin with `inject: false`, you are | ||
required to set the `integrity` and `crossorigin` attributes in your | ||
template as follows: | ||
The plugin installs a hook for `html-webpack-plugin` that adds the | ||
`integrity` attribute automatically when `inject: true` (the default). | ||
This requires `html-webpack-plugin` version `2.21.0` or later. The | ||
`crossorigin` attribute will be set to `anonymous` in this case. | ||
If you're using a template with `html-webpack-plugin` and with | ||
`inject: false`, you can generate the attributes as follows: | ||
```ejs | ||
<% for (var index in htmlWebpackPlugin.files.js) { %> | ||
<script src="<%= htmlWebpackPlugin.files.js[index] %>" | ||
integrity="<%= htmlWebpackPlugin.files.jsIntegrity[index] %>" | ||
crossorigin="anonymous" | ||
<script | ||
src="<%= htmlWebpackPlugin.files.js[index] %>" | ||
integrity="<%= htmlWebpackPlugin.files.jsIntegrity[index] %>" | ||
crossorigin="<%= htmlWebpackPlugin.options.sriCrossOrigin %>" | ||
></script> | ||
@@ -73,27 +67,91 @@ <% } %> | ||
<% for (var index in htmlWebpackPlugin.files.css) { %> | ||
<link href="<%= htmlWebpackPlugin.files.css[index] %>" | ||
integrity="<%= htmlWebpackPlugin.files.cssIntegrity[index] %>" | ||
rel="stylesheet" | ||
crossorigin="anonymous" | ||
> | ||
<link | ||
href="<%= htmlWebpackPlugin.files.css[index] %>" | ||
integrity="<%= htmlWebpackPlugin.files.cssIntegrity[index] %>" | ||
crossorigin="<%= htmlWebpackPlugin.options.sriCrossOrigin %>" | ||
rel="stylesheet" | ||
/> | ||
<% } %> | ||
``` | ||
#### Without HtmlWebpackPlugin | ||
The correct value for the `integrity` attribute can be retrieved from | ||
the `integrity` property of Webpack assets. However, that property is | ||
not copied over by Webpack's `stats` module so you'll have to access | ||
the "original" asset on the `compilation` object. For example: | ||
```javascript | ||
compiler.plugin("done", stats => { | ||
const mainAssetName = stats.toJson().assetsByChunkName.main; | ||
const integrity = stats.compilation.assets[mainAssetName].integrity; | ||
}); | ||
``` | ||
### Options | ||
#### hashFuncNames | ||
Required option, no default value. | ||
An array of strings, each specifying the name of a hash function to be | ||
used for calculating integrity hash values. For example, `['sha256', | ||
'sha512']`. | ||
See [SRI: Cryptographic hash functions](http://www.w3.org/TR/SRI/#cryptographic-hash-functions) | ||
#### enabled | ||
Default value: `true` | ||
When this value is falsy, the plugin doesn't run and no integrity | ||
values are calculated. It is recommended to disable the plugin in | ||
development mode. | ||
#### crossorigin | ||
Default value: `"anonymous"` | ||
When using `HtmlWebpackPlugin({ inject: true })`, this option | ||
specifies the value to be used for the `crossorigin` attribute for | ||
injected assets. | ||
The value will also be available as | ||
`htmlWebpackPlugin.options.sriCrossOrigin` in html-webpack-plugin | ||
templates. | ||
See | ||
[SRI: Cross-origin data leakage](https://www.w3.org/TR/SRI/#cross-origin-data-leakage) and | ||
[MDN: CORS settings attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) | ||
## Caveats | ||
* There is a | ||
[known bug relating to SRI in Chrome versions before 47](https://code.google.com/p/chromium/issues/detail?id=527286) | ||
which will break loading of scripts containing certain UTF-8 | ||
characters. You might want to hold off using SRI if you need to | ||
support older Chrome versions. | ||
### Browser support | ||
## Contributing | ||
Browser support for SRI is currently patchy. Your page will still | ||
work on browsers without support for SRI, but subresources won't be | ||
protected from tampering. | ||
If you have discovered a bug or have a feature suggestion, feel free to create an issue on Github. | ||
See [Can I use Subresource Integrity?](http://caniuse.com/#feat=subresource-integrity) | ||
Pull requests are welcome. Please run `npm test` and `npm run lint` on | ||
your branch before submitting it. | ||
### Broken browser versions | ||
You are also welcome to correct any spelling mistakes or any language issues. | ||
There is a | ||
[known bug relating to SRI in Chrome 45 and 46](https://code.google.com/p/chromium/issues/detail?id=527286) | ||
which will break loading of scripts containing certain UTF-8 | ||
characters. You might want to hold off using SRI if you need to | ||
support these Chrome versions. (Unfortunately, Chrome 45 still | ||
[holds a relatively high market share](https://www.netmarketshare.com/report.aspx?qprid=3&qpaf=&qpcustom=Chrome+45.0&qpcustomb=0) | ||
of around 5% at the time of this writing.) | ||
### Hot Module Replacement | ||
Chunks loaded via Hot Module Replacement (HMR) are not currently | ||
protected. This shouldn't be a problem because HMR is usually used | ||
only in development mode where SRI is not normally needed. | ||
## Further Reading | ||
- [MDN: Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) | ||
## License | ||
@@ -103,2 +161,2 @@ | ||
MIT (see [`LICENSE`](LICENSE)) | ||
MIT (see [LICENSE](LICENSE)) |
20298
303
161