Improve this Doc View Source

Enables CSP (Content Security Policy) support.

This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps.

CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things). For Angular to be CSP compatible there are only two things that we need to do differently:

- don't use `Function` constructor to generate optimized value getters
- don't inject custom stylesheet into the document

AngularJS uses `Function(string)` generated functions as a speed optimization. Applying the `ngCsp` directive will cause Angular to use CSP compatibility mode. When this mode is on AngularJS will evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will be raised.

CSP forbids JavaScript to inline stylesheet rules. In non CSP mode Angular automatically includes some CSS rules (e.g. ngCloak). To make those directives work in CSP mode, include the `angular-csp.css` manually.

Angular tries to autodetect if CSP is active and automatically turn on the CSP-safe mode. This autodetection however triggers a CSP error to be logged in the console:

    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'". Note that
    'script-src' was not explicitly set, so 'default-src' is used as a fallback.

This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp` directive on the root element of the application or on the `angular.js` script tag, whichever appears first in the html document.

Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.

## Directive Info

- This directive executes at priority level 0.

## Usage

- as attribute:

      <html>
      ...
      </html>

## Example

This example shows how to apply the `ngCsp` directive to the `html` tag.

    <!doctype html>
    <html ng-app ng-csp>
    ...
    ...
    </html>

// Note: the suffix `.csp` in the example name triggers // csp mode in our http server!

Edit in Plunker

    <div ng-controller="MainController as ctrl">
      <div>
        <button ng-click="ctrl.inc()" id="inc">Increment</button>
        <span id="counter">
          {{ctrl.counter}}
        </span>
      </div>

      <div>
        <button ng-click="ctrl.evil()" id="evil">Evil</button>
        <span id="evilError">
          {{ctrl.evilError}}
        </span>
      </div>
    </div>

    angular.module('cspExample', [])
    .controller('MainController', function() {
       this.counter = 0;
       this.inc = function() {
         this.counter++;
       };
       this.evil = function() {
         // jshint evil:true
         try {
           eval('1+2');
         } catch (e) {
           this.evilError = e.message;
         }
       };
     });

    var util, webdriver;

    var incBtn = element(by.id('inc'));
    var counter = element(by.id('counter'));
    var evilBtn = element(by.id('evil'));
    var evilError = element(by.id('evilError'));

    function getAndClearSevereErrors() {
      return browser.manage().logs().get('browser').then(function(browserLog) {
        return browserLog.filter(function(logEntry) {
          return logEntry.level.value > webdriver.logging.Level.WARNING.value;
        });
      });
    }

    function clearErrors() {
      getAndClearSevereErrors();
    }

    function expectNoErrors() {
      getAndClearSevereErrors().then(function(filteredLog) {
        expect(filteredLog.length).toEqual(0);
        if (filteredLog.length) {
          console.log('browser console errors: ' + util.inspect(filteredLog));
        }
      });
    }

    function expectError(regex) {
      getAndClearSevereErrors().then(function(filteredLog) {
        var found = false;
        filteredLog.forEach(function(log) {
          if (log.message.match(regex)) {
            found = true;
          }
        });
        if (!found) {
          throw new Error('expected an error that matches ' + regex);
        }
      });
    }

    beforeEach(function() {
      util = require('util');
      webdriver = require('protractor/node_modules/selenium-webdriver');
    });

    // For now, we only test on Chrome,
    // as Safari does not load the page with Protractor's injected scripts,
    // and Firefox webdriver always disables content security policy (#6358)
    if (browser.params.browser !== 'chrome') {
      return;
    }

    it('should not report errors when the page is loaded', function() {
      // clear errors so we are not dependent on previous tests
      clearErrors();
      // Need to reload the page as the page is already loaded when
      // we come here
      browser.driver.getCurrentUrl().then(function(url) {
        browser.get(url);
      });
      expectNoErrors();
    });

    it('should evaluate expressions', function() {
      expect(counter.getText()).toEqual('0');
      incBtn.click();
      expect(counter.getText()).toEqual('1');
      expectNoErrors();
    });

    it('should throw and report an error when using "eval"', function() {
      evilBtn.click();
      expect(evilError.getText()).toMatch(/Content Security Policy/);
      expectError(/Content Security Policy/);
    });