From 3ba12e90cbb68001c5708e1ccace75c27c9b804c Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Mon, 14 Jul 2014 10:30:32 -0700 Subject: [PATCH] fix(csp): fix autodetection of CSP + better docs CSP spec got changed and it is no longer possible to autodetect if a policy is active without triggering a CSP error: https://github.com/w3c/webappsec/commit/18882953ce2d8afca25f685557fef0e0471b2c9a Now we use `new Function('')` to detect if CSP is on. To prevent error from this detection to show up in console developers have to use the ngCsp directive. (This problem became more severe after our recent removal of `simpleGetterFn` which made us depend on function constructor for all expressions.) Closes #8162 Closes #8191 --- src/Angular.js | 23 ++++++++++++++++++----- src/ng/directive/ngCsp.js | 25 +++++++++++++++++++------ test/AngularSpec.js | 10 ++++++---- 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/src/Angular.js b/src/Angular.js index eaaf2f37bc75..f75c17dd7e84 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -921,12 +921,25 @@ function equals(o1, o2) { return false; } +var csp = function() { + if (isDefined(csp.isActive_)) return csp.isActive_; + + var active = !!(document.querySelector('[ng-csp]') || + document.querySelector('[data-ng-csp]')); + + if (!active) { + try { + /* jshint -W031, -W054 */ + new Function(''); + /* jshint +W031, +W054 */ + } catch (e) { + active = true; + } + } + + return (csp.isActive_ = active); +}; -function csp() { - return (document.securityPolicy && document.securityPolicy.isActive) || - (document.querySelector && - !!(document.querySelector('[ng-csp]') || document.querySelector('[data-ng-csp]'))); -} function concat(array1, array2, index) { diff --git a/src/ng/directive/ngCsp.js b/src/ng/directive/ngCsp.js index 38508bb41201..691da10d9a87 100644 --- a/src/ng/directive/ngCsp.js +++ b/src/ng/directive/ngCsp.js @@ -11,8 +11,10 @@ * This is necessary when developing things like Google Chrome Extensions. * * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things). - * For us to be compatible, we just need to implement the "getterFn" in $parse without violating - * any of these restrictions. + * 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 @@ -23,7 +25,18 @@ * includes some CSS rules (e.g. {@link ng.directive:ngCloak ngCloak}). * To make those directives work in CSP mode, include the `angular-csp.css` manually. * - * In order to use this feature put the `ngCsp` directive on the root element of the application. + * 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.* * @@ -38,6 +51,6 @@ ``` */ -// ngCsp is not implemented as a proper directive any more, because we need it be processed while we bootstrap -// the system (before $parse is instantiated), for this reason we just have a csp() fn that looks for ng-csp attribute -// anywhere in the current doc +// ngCsp is not implemented as a proper directive any more, because we need it be processed while we +// bootstrap the system (before $parse is instantiated), for this reason we just have +// the csp.isActive() fn that looks for ng-csp attribute anywhere in the current doc diff --git a/test/AngularSpec.js b/test/AngularSpec.js index 6a74c6c615b9..7e8b7a50ec6f 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -413,14 +413,15 @@ describe('angular', function() { describe('csp', function() { - var originalSecurityPolicy; + var originalFunction; beforeEach(function() { - originalSecurityPolicy = document.securityPolicy; + originalFunction = window.Function; }); afterEach(function() { - document.securityPolicy = originalSecurityPolicy; + window.Function = originalFunction; + delete csp.isActive_; }); @@ -430,10 +431,11 @@ describe('angular', function() { it('should return true if CSP is autodetected via CSP v1.1 securityPolicy.isActive property', function() { - document.securityPolicy = {isActive: true}; + window.Function = function() { throw new Error('CSP test'); }; expect(csp()).toBe(true); }); + it('should return the true when CSP is enabled manually via [ng-csp]', function() { spyOn(document, 'querySelector').andCallFake(function(selector) { if (selector == '[ng-csp]') return {};