Skip to content

Commit 4e3fc00

Browse files
author
Ashwin Bhat
committed
skip hashing if unsafe-inline is present
1 parent 271b6ae commit 4e3fc00

File tree

2 files changed

+129
-13
lines changed

2 files changed

+129
-13
lines changed

plugin.js

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class CspHtmlWebpackPlugin {
2626
constructor(policy = {}, additionalOpts = {}) {
2727
// the policy we want to use
2828
this.policy = Object.assign({}, defaultPolicy, policy);
29+
this.userPolicy = policy;
2930

3031
// the additional options that this plugin allows
3132
this.opts = Object.assign({}, defaultAdditionalOpts, additionalOpts);
@@ -114,20 +115,25 @@ class CspHtmlWebpackPlugin {
114115
}
115116

116117
const policyObj = JSON.parse(JSON.stringify(this.policy));
117-
118-
const inlineSrc = $('script:not([src])')
119-
.map((i, element) => this.hash($(element).html()))
120-
.get();
121-
const inlineStyle = $('style:not([href])')
122-
.map((i, element) => this.hash($(element).html()))
123-
.get();
124-
125-
// Wrapped in flatten([]) to handle both when policy is a string and an array
126-
policyObj['script-src'] = flatten([policyObj['script-src']]).concat(
127-
inlineSrc
118+
const parsedUserPolicy = JSON.parse(JSON.stringify(this.userPolicy));
119+
120+
// If the user policy contains 'unsafe-inline' for either script-src or style-src, we need to
121+
// avoid hashing the existing script tags, so as to avoid implicitly disabling the
122+
// 'unsafe-inline' preference.
123+
124+
policyObj['script-src'] = this.createPolicyObj(
125+
$,
126+
'script-src',
127+
'script:not([src])',
128+
policyObj,
129+
parsedUserPolicy
128130
);
129-
policyObj['style-src'] = flatten([policyObj['style-src']]).concat(
130-
inlineStyle
131+
policyObj['style-src'] = this.createPolicyObj(
132+
$,
133+
'style-src',
134+
'style:not([href])',
135+
policyObj,
136+
parsedUserPolicy
131137
);
132138

133139
metaTag.attr('content', this.buildPolicy(policyObj));
@@ -138,6 +144,28 @@ class CspHtmlWebpackPlugin {
138144
return compileCb(null, htmlPluginData);
139145
}
140146

147+
/**
148+
* Helper function for transforming script-src and style-src policies.
149+
* @param {object} $ - the Cheerio instance
150+
* @param {string} policyName - one of 'script-src' and 'style-src'
151+
* @param {string} selector - a Cheerio selector string for getting the hashable elements for this policy
152+
* @param {object} policyObj - the working CSP policy object
153+
* @param {object} userPolicyObj - the sanitized CSP policy object provided by the user
154+
* @return {object} the new policy for `policyName`
155+
*/
156+
createPolicyObj($, policyName, selector, policyObj, userPolicyObj) {
157+
// Wrapped in flatten([]) to handle both when policy is a string and an array
158+
const flattenedUserPolicy = flatten(userPolicyObj[policyName]);
159+
if (flattenedUserPolicy.includes("'unsafe-inline'")) {
160+
return policyObj[policyName];
161+
}
162+
163+
const hashes = $(selector)
164+
.map((i, element) => this.hash($(element).html()))
165+
.get();
166+
return flatten([policyObj[policyName]]).concat(hashes);
167+
}
168+
141169
/**
142170
* Hooks into webpack to collect assets and hash them, build the policy, and add it into our HTML template
143171
* @param compiler

spec/plugin.spec.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,94 @@ describe('CspHtmlWebpackPlugin', () => {
177177
);
178178
});
179179

180+
describe("when the user-specified script-src policy contains 'unsafe-inline'", () => {
181+
it('skips the hashing of the scripts it finds', done => {
182+
const webpackConfig = {
183+
entry: path.join(__dirname, 'fixtures/index.js'),
184+
output: {
185+
path: OUTPUT_DIR,
186+
filename: 'index.bundle.js'
187+
},
188+
mode: 'none',
189+
plugins: [
190+
new HtmlWebpackPlugin({
191+
filename: path.join(OUTPUT_DIR, 'index.html'),
192+
template: path.join(__dirname, 'fixtures', 'with-js.html'),
193+
inject: 'body'
194+
}),
195+
new CspHtmlWebpackPlugin({
196+
'base-uri': ["'self'", 'https://slack.com'],
197+
'font-src': ["'self'", "'https://a-slack-edge.com'"],
198+
'script-src': ["'self'", "'unsafe-inline'"],
199+
'style-src': ["'self'"]
200+
})
201+
]
202+
};
203+
204+
testCspHtmlWebpackPlugin(
205+
webpackConfig,
206+
'index.html',
207+
(cspPolicy, _, doneFn) => {
208+
const expected =
209+
"base-uri 'self' https://slack.com;" +
210+
" object-src 'none';" +
211+
" script-src 'self' 'unsafe-inline';" +
212+
" style-src 'self';" +
213+
" font-src 'self' 'https://a-slack-edge.com'";
214+
215+
expect(cspPolicy).toEqual(expected);
216+
217+
doneFn();
218+
},
219+
done
220+
);
221+
});
222+
});
223+
224+
describe("when the user-specified style-src policy contains 'unsafe-inline'", () => {
225+
it('skips the hashing of the styles it finds', done => {
226+
const webpackConfig = {
227+
entry: path.join(__dirname, 'fixtures/index.js'),
228+
output: {
229+
path: OUTPUT_DIR,
230+
filename: 'index.bundle.js'
231+
},
232+
mode: 'none',
233+
plugins: [
234+
new HtmlWebpackPlugin({
235+
filename: path.join(OUTPUT_DIR, 'index.html'),
236+
template: path.join(__dirname, 'fixtures', 'with-css.html'),
237+
inject: 'body'
238+
}),
239+
new CspHtmlWebpackPlugin({
240+
'base-uri': ["'self'", 'https://slack.com'],
241+
'font-src': ["'self'", "'https://a-slack-edge.com'"],
242+
'script-src': ["'self'"],
243+
'style-src': ["'self'", "'unsafe-inline'"]
244+
})
245+
]
246+
};
247+
248+
testCspHtmlWebpackPlugin(
249+
webpackConfig,
250+
'index.html',
251+
(cspPolicy, _, doneFn) => {
252+
const expected =
253+
"base-uri 'self' https://slack.com;" +
254+
" object-src 'none';" +
255+
" script-src 'self';" +
256+
" style-src 'self' 'unsafe-inline';" +
257+
" font-src 'self' 'https://a-slack-edge.com'";
258+
259+
expect(cspPolicy).toEqual(expected);
260+
261+
doneFn();
262+
},
263+
done
264+
);
265+
});
266+
});
267+
180268
it('removes the empty Content Security Policy meta tag if enabled is the bool false', done => {
181269
const webpackConfig = {
182270
entry: path.join(__dirname, 'fixtures/index.js'),

0 commit comments

Comments
 (0)