I'm using jQuery version 3.1.1 and I'm trying to implement Content Security Policy (CSP) directives on my webpage.
I'm getting the following error:
Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' 'nonce-c20t41c7-73c6-4bf9-fde8-24a7b35t5f71'". Either the 'unsafe-inline' keyword, a hash ('sha256-KAcpKskREkEQf5B3mhDTonpPg34XnzaUC5IoBrOUrwY='), or a nonce ('nonce-...') is required to enable inline execution.
The error is produced on line 82 of the main jquery.js script file. The content of this line is:
doc.head.appendChild( script ).parentNode.removeChild( script );
Basically, it adds an inline script tag to the DOM, that violates the CSP.
I do not want to use 'unsafe-inline'
. Is there another way to circumvent this error?
As you can see on the CSP violation, I'm using CSP level 2 (nonce), but it is ignored. Would it be possible (somehow) to inform jQuery to use this nonce when appending the script tag?
This is how the HTML looks like (using an Express.js template for nonce)
<script nonce="<%=nonce%>" type="text/javascript" src="jquery.js"></script>
Once the HTML is rendered, the nonce attribute on the script tag matches the CSP nonce directive sent by the server.
It does work with plain JavaScript:
<script nonce="<%=nonce%>" type="text/javascript">
var userEmail = "<%=user.user.email%>";
</script>
Without the nonce attribute, this script tag would violate the 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):
type="false/"
attribute to the script tagtype
attributesrc
attribute is present it retrieves the script via Ajax (didn't investigate this further)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)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With