-
-
Notifications
You must be signed in to change notification settings - Fork 97
Add support for subresource integrity (#506) #570
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a876283
d62285f
72455f7
98d5f14
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# Subresource integrity | ||
It's a cryptographic hash that helps browsers check that the served js or css file has not been tampered in any way. | ||
|
||
[MDN - Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) | ||
|
||
## Important notes | ||
- If you somehow modify the file after the hash was generated, it will automatically be considered as tampered, and the browser will not allow it to be executed. | ||
- Enabling subresource integrity generation, will change the structure of `manifest.json`. Keep that in mind if you utilize this file in any other custom implementation. | ||
|
||
Before: | ||
```json | ||
{ | ||
"application.js": "/path_to_asset" | ||
} | ||
``` | ||
|
||
After: | ||
```json | ||
{ | ||
"application.js": { | ||
"src": "/path_to_asset", | ||
"integrity": "<sha256-hash> <sha384-hash> <sha512-hash>" | ||
} | ||
} | ||
``` | ||
|
||
## Possible CORS issues | ||
Enabling subresource integrity for an asset, actually enforces CORS checks on that resource too. Which means that | ||
if you haven't set that up properly beforehand, it will probably lead to CORS errors with cached assets. | ||
|
||
[MDN - How browsers handle Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity#how_browsers_handle_subresource_integrity) | ||
|
||
## Configuration | ||
|
||
By default, this setting is disabled, to ensure backwards compatibility, and let developers adapt at their own pace. | ||
This may change in the future, as it is a very nice security feature, and it should be enabled by default. | ||
|
||
To enable it, just add this in `shakapacker.yml` | ||
```yml | ||
integrity: | ||
enabled: true | ||
``` | ||
|
||
For further customization, you can also utilize the options `hash_functions` that control the functions used to generate | ||
integrity hashes. And `cross_origin` that sets the cross-origin loading attribute. | ||
|
||
```yml | ||
integrity: | ||
enabled: true | ||
hash_functions: ["sha256", "sha384", "sha512"] | ||
cross_origin: "anonymous" # or "use-credentials" | ||
``` | ||
|
||
This will utilize under the hood webpack-subresource-integrity plugin and will modify `manifest.json` to include integrity hashes. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,6 +55,16 @@ default: &default | |
# SHAKAPACKER_ASSET_HOST will override both configurations. | ||
# asset_host: custom-path | ||
|
||
# Utilizing webpack-subresource-integrity plugin, will generate integrity hashes for all entries in manifest.json | ||
# https://github.com/waysact/webpack-subresource-integrity/tree/main/webpack-subresource-integrity | ||
integrity: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note: this can be done as a follow-up, but should we consider shorthands such as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure i understand, can you elaborate a bit? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so instead of this:
you'd be able to do
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤔 I can definitely try. I'll try to come back to this within a few days, if i'm still stuck on how to do it we can leave it as a follow up There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
enabled: false | ||
# Which cryptographic function(s) to use, for generating the integrity hash(es). Default sha-384. Other possible values sha256, sha512 | ||
hash_functions: ["sha384"] | ||
# Default "anonymous". Other possible value "use-credentials" | ||
# https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity#cross-origin_resource_sharing_and_subresource_integrity | ||
cross_origin: "anonymous" | ||
|
||
development: | ||
<<: *default | ||
compile: true | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,6 +46,7 @@ | |
"thenify": "^3.3.1", | ||
"webpack": "5.93.0", | ||
"webpack-assets-manifest": "^5.0.6", | ||
"webpack-subresource-integrity": "^5.1.0", | ||
"webpack-merge": "^5.8.0" | ||
}, | ||
"peerDependencies": { | ||
|
@@ -60,6 +61,7 @@ | |
"terser-webpack-plugin": "^5.3.1", | ||
"webpack": "^5.76.0", | ||
"webpack-assets-manifest": "^5.0.6 || ^6.0.0", | ||
"webpack-subresource-integrity": "^5.1.0", | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
panagiotisplytas marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"webpack-cli": "^4.9.2 || ^5.0.0 || ^6.0.0", | ||
"webpack-dev-server": "^4.9.0 || ^5.0.0", | ||
"webpack-merge": "^5.8.0 || ^6.0.0" | ||
|
@@ -70,6 +72,9 @@ | |
}, | ||
"@types/webpack": { | ||
"optional": true | ||
}, | ||
"webpack-subresource-integrity": { | ||
"optional": true | ||
} | ||
}, | ||
"packageManager": "[email protected]", | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -86,7 +86,9 @@ const getPlugins = () => { | |||||||||||||||||
writeToDisk: true, | ||||||||||||||||||
output: config.manifestPath, | ||||||||||||||||||
entrypointsUseAssets: true, | ||||||||||||||||||
publicPath: config.publicPathWithoutCDN | ||||||||||||||||||
publicPath: config.publicPathWithoutCDN, | ||||||||||||||||||
integrity: config.integrity.enabled, | ||||||||||||||||||
integrityHashes: config.integrity.hash_functions | ||||||||||||||||||
}) | ||||||||||||||||||
] | ||||||||||||||||||
|
||||||||||||||||||
|
@@ -105,6 +107,22 @@ const getPlugins = () => { | |||||||||||||||||
) | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
if ( | ||||||||||||||||||
moduleExists("webpack-subresource-integrity") && | ||||||||||||||||||
config.integrity.enabled | ||||||||||||||||||
) { | ||||||||||||||||||
const { | ||||||||||||||||||
SubresourceIntegrityPlugin | ||||||||||||||||||
} = require("webpack-subresource-integrity") | ||||||||||||||||||
|
||||||||||||||||||
plugins.push( | ||||||||||||||||||
new SubresourceIntegrityPlugin({ | ||||||||||||||||||
hashFuncNames: config.integrity.hash_functions, | ||||||||||||||||||
enabled: isProduction | ||||||||||||||||||
}) | ||||||||||||||||||
) | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
return plugins | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
|
@@ -121,7 +139,12 @@ module.exports = { | |||||||||||||||||
// https://webpack.js.org/configuration/output/#outputhotupdatechunkfilename | ||||||||||||||||||
hotUpdateChunkFilename: "js/[id].[fullhash].hot-update.js", | ||||||||||||||||||
path: config.outputPath, | ||||||||||||||||||
publicPath: config.publicPath | ||||||||||||||||||
publicPath: config.publicPath, | ||||||||||||||||||
|
||||||||||||||||||
// This is required for SRI to work. | ||||||||||||||||||
crossOriginLoading: config.integrity.enabled | ||||||||||||||||||
? config.integrity.cross_origin | ||||||||||||||||||
: false | ||||||||||||||||||
Comment on lines
+144
to
+147
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Config key mismatch will disable
- crossOriginLoading: config.integrity.enabled
- ? config.integrity.cross_origin
+ crossOriginLoading: config.integrity.enabled
+ ? config.integrity.cross_origin_loading
: false Please align the key name here (or in the docs/config) to avoid silent mis-configuration. 📝 Committable suggestion
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was named intentionally like that to cover both There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||
}, | ||||||||||||||||||
entry: getEntryObject(), | ||||||||||||||||||
resolve: { | ||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: should we just switch to always generating an object going forward? (either now or in the next major)
while that'd technically make the file a little bigger in all cases, we'll only be talking a few bytes and ultimately SRI should be used whenever possible...
We could do this as a follow up, but I'm thinking if so we could add a new migrate-now kind of option for having the manifest always generated with
{ src: ... }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 Eventually, yes, i think we should have the same kind of output in the
manifest.json
across all environments (+/- integrity hashes). For consistency, and also to make the implementation & tests of SRI (and other features like that ) a bit simpler.Having said that, and looking at the
webpack-assets-manifest -plugin
i am not sure how can we achieve that. Tried playing a bit withcustomize
function in the past, but it went comically bad 😅 .I may be missing something obvious tho.
I'll be happy to try if you have any suggestion in mind .