Angular.js: ngCsp not enough to avoid CSP errors if using inline javascript

Created on 30 Mar 2016  路  2Comments  路  Source: angular/angular.js

Following angular documentation for ngCsp looks like just by using the directive is not enough to avoid errors if you use inline scripts. Even after following the guidelines we were still getting randomly the below errors causing angular to stop working and consequently blank content. The reason why it was random was that we had inline javascript coming up from Google mod-pagespeed randomly in the page.

I think Angular should probably just detect if ng-csp is being used to avoid executing inline scripts in such case. It could also come up with a non eval way if there is still a need to run inline scripts just as it does with the "Angular's built-in subset of jQuery, called 'jQuery lite' or jqLite". At a minimum the documentation should state that currently the ng-csp directive will not work if at least if there is inline javascript inside the ng-if directive. This is a problem even if the server sends unsafe-inline like in the below Apache example:
Header set Content-Security-Policy: "default-src 'self' 'unsafe-inline'"

To replicate this issue:
Step 1: Use the code below to confirm a Chrome error when your server uses "Content-Security-Policy:default-src 'self' 'unsafe-inline'"

<!DOCTYPE html>
<html lang="en" ng-app  ng-csp>
  <head>
    <script type="text/javascript" charset="utf-8" src="jquery.js"></script>
    <script type="text/javascript" charset="utf-8" src="angular.js"></script>
  </head>
  <body>
    <div ng-if="true">
      <script>console.log('foo');</script>
    </div>
  </body>
</html>

Step 2: Swap jquery and angular script inclusions to confirm the error goes away

Here is a typical stacktrace:

angular.js:13424 EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "default-src 'self' 'unsafe-inline'". at Function.jQuery.extend.globalEval (jquery.js:343) at domManip (jquery.js:5290) at jQuery.fn.extend.after (jquery.js:5456) at domInsert (angular.js:5189) at Object.$provide.$get.$$animateQueue.enter (angular.js:5352) at angular.js:25332 at $$sanitizeUriProvider.$get.node (angular.js:8212) at angular.js:8551 at boundTranscludeFn (angular.js:8350) at controllersBoundTransclude (angular.js:9072)

misc core low investigation not core

Most helpful comment

First of all ngCsp doesn't have anything to do with the issue. ngCsp just tells Angular to not internally use features that would violate the CSP (and indeed it doesn't).

ngIf will call .after() in order to insert the <div ...><script>...</script></div> element. The problem happens because of how jQuery (< 3.x) handles inserting DOM fragments that contain script tags:

  1. It disables the scripts (by setting an unknown type attribute).
  2. Inserts the elements into the DOM.
  3. Removes the type attribute from the scripts.
  4. Executes the code contained in the scripts.

jQuery 2.x implements (4) in the following way (see core.js):

  • If the code starts with 'use strict', it will create a new script tag, insert the code into it, then insert the script into the DOM and immediately remove it.
  • If the code doesn't start with 'use strict', it will use eval(), which violates the CSP.

FWIW, jQuery 3.x always uses the first approach, so it doesn't trigger the error. (See core.js and DOMEval.js.)

When using jQuery 2.x (or older), you can avoid the error by prepending your script code with 'use strict'. In jQuery 3.x, that won't be an issue any more.

I don't know if there is any other way to disable eval() in jQuery 2.x - I couldn't find one.
/cc @mgol

Closing as this is not Angular-related (but feel free to continue the discussion below).


BTW, you should keep in mnd that there is a difference in behavior between jqLite and jQuery when dynamically inserting/removing script tags:
With jqLite the code won't be executed each time you re-insert it into the DOM (e.g. by changing the ngIf condition from false to true). With jQuery it will be executed every time.

All 2 comments

First of all ngCsp doesn't have anything to do with the issue. ngCsp just tells Angular to not internally use features that would violate the CSP (and indeed it doesn't).

ngIf will call .after() in order to insert the <div ...><script>...</script></div> element. The problem happens because of how jQuery (< 3.x) handles inserting DOM fragments that contain script tags:

  1. It disables the scripts (by setting an unknown type attribute).
  2. Inserts the elements into the DOM.
  3. Removes the type attribute from the scripts.
  4. Executes the code contained in the scripts.

jQuery 2.x implements (4) in the following way (see core.js):

  • If the code starts with 'use strict', it will create a new script tag, insert the code into it, then insert the script into the DOM and immediately remove it.
  • If the code doesn't start with 'use strict', it will use eval(), which violates the CSP.

FWIW, jQuery 3.x always uses the first approach, so it doesn't trigger the error. (See core.js and DOMEval.js.)

When using jQuery 2.x (or older), you can avoid the error by prepending your script code with 'use strict'. In jQuery 3.x, that won't be an issue any more.

I don't know if there is any other way to disable eval() in jQuery 2.x - I couldn't find one.
/cc @mgol

Closing as this is not Angular-related (but feel free to continue the discussion below).


BTW, you should keep in mnd that there is a difference in behavior between jqLite and jQuery when dynamically inserting/removing script tags:
With jqLite the code won't be executed each time you re-insert it into the DOM (e.g. by changing the ngIf condition from false to true). With jQuery it will be executed every time.

Awesome. Thanks gkalpak. This makes sense.

Was this page helpful?
0 / 5 - 0 ratings