From 9bfcdc08b90a40edfdfd1067d287ececaa15ad44 Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Thu, 24 Nov 2016 01:44:58 +0100 Subject: [PATCH] feat(security): do not bootstrap from unknown schemes with a different origin --- src/Angular.js | 10 ++++++---- test/AngularSpec.js | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/Angular.js b/src/Angular.js index 97b061eafc00..c01614db93b5 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -1485,12 +1485,14 @@ function allowAutoBootstrap(document) { var src = document.currentScript.getAttribute('src'); var link = document.createElement('a'); link.href = src; - var scriptProtocol = link.protocol; - var docLoadProtocol = document.location.protocol; - if (docLoadProtocol === scriptProtocol) { + if (document.location.origin === link.origin) { + // Same-origin resources are always allowed, even for non-whitelisted schemes. return true; } - switch (scriptProtocol) { + // Disabled bootstrapping unless angular.js was loaded from a known scheme used on the web. + // This is to prevent angular.js bundled with browser extensions from being used to bypass the + // content security policy in web pages and other browser extensions. + switch (link.protocol) { case 'http:': case 'https:': case 'ftp:': diff --git a/test/AngularSpec.js b/test/AngularSpec.js index 1c07ef453afe..10540ecc36e5 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -1683,7 +1683,41 @@ describe('angular', function() { dealoc(appElement); }); + it('should bootstrap from an extension into an extension document for same-origin documents only', function() { + if (msie) return; // IE does not support document.currentScript (nor extensions with protocol), so skip test. + + // Extension URLs are browser-specific, so we must choose a scheme that is supported by the browser to make + // sure that the URL is properly parsed. + var extensionScheme; + var userAgent = window.navigator.userAgent; + if (/Firefox\//.test(userAgent)) { + extensionScheme = 'moz-extension'; + } else if (/Edge\//.test(userAgent)) { + extensionScheme = 'ms-browser-extension'; + } else if (/Chrome\//.test(userAgent)) { + extensionScheme = 'chrome-extension'; + } else if (/Safari\//.test(userAgent)) { + extensionScheme = 'safari-extension'; + } else { + extensionScheme = 'browserext'; // Upcoming standard scheme. + } + + var src = extensionScheme + '://something'; + // Fake a minimal document object (the actual document.currentScript is readonly). + var fakeDoc = { + currentScript: { getAttribute: function() { return src; } }, + location: {protocol: extensionScheme + ':', origin: extensionScheme + '://something'}, + createElement: document.createElement.bind(document) + }; + expect(allowAutoBootstrap(fakeDoc)).toBe(true); + + src = extensionScheme + '://something-else'; + expect(allowAutoBootstrap(fakeDoc)).toBe(false); + }); + it('should not bootstrap from an extension into a non-extension document', function() { + if (msie) return; // IE does not support document.currentScript (nor extensions with protocol), so skip test. + var src = 'resource://something'; // Fake a minimal document object (the actual document.currentScript is readonly). var fakeDoc = {