jQuery 3.1.1 violation of CSP directive
It looks like a bug or quirk of jQuery how it appends inline scripts ends up discarding all of their attributes and I can't see an obvious way of fixing it to test it I used the following HTML:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="default-src http://localhost 'nonce-123456' ; child-src 'none'; object-src 'none'; script-src 'nonce-123456';">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.js" nonce="123456"></script> <!-- HTML nonce works -->
<script nonce="123456">
// This works
console.log('Inline nonce works');
// This will also work
var s = document.createElement('script');
s.setAttribute('nonce', '123456');
s.textContent = 'console.log("Dynamically generated inline tag works")';
document.head.appendChild(s);
// This won't work
var s2 = document.createElement('script');
s2.setAttribute('nonce', '123456');
s2.textContent = 'console.log("Dynamically generated inline tag appended via jQuery doesn\'t work")';
$(document.head).append(s2); // This will throw a CSP error
</script>
</head>
<body>
</body>
</html>
When using jQuery to append it goes through the following process (reduced a little):
- Creates a document fragment and appends a script tag to it
- Applies a
type="false/"
attribute to the script tag - Removes the
type
attribute - If a
src
attribute is present it retrieves the script via Ajax (didn't investigate this further) - If not it runs
DOMEval(node.textContent.replace(rcleanScript, ""), doc)
DomEval
looks like this (with added comments):
doc = doc || document;
var script = doc.createElement( "script" );
script.textContent = code;
doc.head.appendChild( script ).parentNode.removeChild( script );
As you can see, no attributes would carry over to the new element before it was appended and as such CSP fails.
The solution would be to just use native JavaScript to append the element as opposed to jQuery or possibly wait on a bug fix/response to your report. I'm unsure what their reasoning would be to exclude attributes in this manner for inline script tags maybe a security feature?
The following should achieve what you want without jQuery - just set the textContent attribute to your JavaScript source code.
var script = document.createElement('script');
script.setAttribute('nonce', '<%=nonce%>');
script.textContent = '// Code here';
document.head.appendChild(script);
So essentially why that particular line throws the error is that the appended tag is actually a new tag with the same code and no attributes applied to it and as it has no nonce
it's rejected by CSP.
Update: I've patched jQuery to fix this issue (is 3.1.2-pre patched but passing all tests), if you used my last fix I recommend updating to this version!
Minified: http://pastebin.com/gcLexN7z
Un-minified: http://pastebin.com/AEvzir4H
The branch is available here: https://github.com/Brian-Aykut/jquery/tree/3541-csp
Issue link: https://github.com/jquery/jquery/issues/3541
Changes in the code:
Line ~76 replace DOMEval
function with:
function DOMEval( code, doc, attr ) {
doc = doc || document;
attr = attr || {};
var script = doc.createElement( "script" );
for ( var key in attr ) {
if ( attr.hasOwnProperty( key ) ) {
script.setAttribute( key, attr[ key ] );
}
}
script.text = code;
doc.head.appendChild( script ).parentNode.removeChild( script );
}
Add attr
to var
statement on ~line 5717 to
var fragment, first, scripts, hasScripts, node, doc, attr,
Change else
body near line 5790 to:
attr = {};
if ( node.hasAttribute && node.hasAttribute( "nonce" ) ) {
attr.nonce = node.getAttribute( "nonce" );
}
DOMEval( node.textContent.replace( rcleanScript, "" ), doc, attr );
OK, you just want to include jQuery, so you need to redefined the function appendChild (to disable it) before the jQuery script and after delete your custom function.
<script>
var oldAppend = document.head.appendChild
document.head.appendChild = function(script){
if(script && script.tagName=='SCRIPT'){
document.createElement('fakeHead').appendChild(script)//because script need a parent in the line that create the error
return script
}
return oldAppend.apply(this,arguments)
}
</script>
<script nonce="<%=nonce%>" type="text/javascript" src="jquery.js"></script>
<script>
document.head.appendChild = oldAppend
</script>
Try this. It will redefine the append function of jQuery:
var oldAppend = $.fn.append
$.fn.append = function($el){
var dom = ($el instanceOf $) ? $el[0] : $el
if(dom && dom.tagName=='SCRIPT'){
this[0].appendChild(dom)
return this
}
return oldAppend.apply(this,arguments)
}