Skip to content

Commit 04cad41

Browse files
rjametpetebacondarwin
authored andcommitted
fix($compile): secure link[href] as a RESOURCE_URLs in $sce.
User-controlled imports or stylesheets can run script in your origin, which warrants that we require that they are safe `RESOURCE_URL`s. Closes angular#14687 BREAKING CHANGE `link[href]` attributes are now protected via `$sce`, which prevents interpolated values that fail the `RESOURCE_URL` context tests from being used in interpolation. For example if the application is running at `https://docs.angularjs.org` then the following will fail: ``` <link href="{{ 'http://mydomain.org/unsafe.css' }}" rel="stylesheet"> ``` By default, `RESOURCE_URL` safe URLs are only allowed from the same domain and protocol as the application document. To use URLs from other domains and/or protocols, you may either whitelist them or wrap it into a trusted value by calling `$sce.trustAsResourceUrl(url)`.
1 parent 3b4bfa1 commit 04cad41

File tree

2 files changed

+33
-0
lines changed

2 files changed

+33
-0
lines changed

src/ng/compile.js

+2
Original file line numberDiff line numberDiff line change
@@ -2971,6 +2971,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
29712971
// maction[xlink:href] can source SVG. It's not limited to <maction>.
29722972
if (attrNormalizedName === "xlinkHref" ||
29732973
(tag === "form" && attrNormalizedName === "action") ||
2974+
// links can be stylesheets or imports, which can run script in the current origin
2975+
(tag === "link" && attrNormalizedName === "href") ||
29742976
(tag !== "img" && (attrNormalizedName === "src" ||
29752977
attrNormalizedName === "ngSrc"))) {
29762978
return $sce.RESOURCE_URL;

test/ng/compileSpec.js

+31
Original file line numberDiff line numberDiff line change
@@ -10171,6 +10171,7 @@ describe('$compile', function() {
1017110171
"loading resource from url not allowed by $sceDelegate policy. URL: javascript:doTrustedStuff()");
1017210172
}));
1017310173

10174+
1017410175
it('should pass through $sce.trustAs() values in action attribute', inject(function($compile, $rootScope, $sce) {
1017510176
/* jshint scripturl:true */
1017610177
element = $compile('<form action="{{testUrl}}"></form>')($rootScope);
@@ -10181,6 +10182,36 @@ describe('$compile', function() {
1018110182
}));
1018210183
});
1018310184

10185+
describe('link[href]', function() {
10186+
it('should reject invalid RESOURCE_URLs', inject(function($compile, $rootScope) {
10187+
element = $compile('<link href="{{testUrl}}" rel="stylesheet" />')($rootScope);
10188+
$rootScope.testUrl = "https://evil.example.org/css.css";
10189+
expect(function() { $rootScope.$apply(); }).toThrowMinErr(
10190+
"$interpolate", "interr", "Can't interpolate: {{testUrl}}\nError: [$sce:insecurl] Blocked " +
10191+
"loading resource from url not allowed by $sceDelegate policy. URL: " +
10192+
"https://evil.example.org/css.css");
10193+
}));
10194+
10195+
it('should accept valid RESOURCE_URLs', inject(function($compile, $rootScope, $sce) {
10196+
element = $compile('<link href="{{testUrl}}" rel="stylesheet" />')($rootScope);
10197+
10198+
$rootScope.testUrl = "./css1.css";
10199+
$rootScope.$apply();
10200+
expect(element.attr('href')).toContain('css1.css');
10201+
10202+
$rootScope.testUrl = $sce.trustAsResourceUrl('https://elsewhere.example.org/css2.css');
10203+
$rootScope.$apply();
10204+
expect(element.attr('href')).toContain('https://elsewhere.example.org/css2.css');
10205+
}));
10206+
10207+
it('should accept valid constants', inject(function($compile, $rootScope) {
10208+
element = $compile('<link href="https://elsewhere.example.org/css2.css" rel="stylesheet" />')($rootScope);
10209+
10210+
$rootScope.$apply();
10211+
expect(element.attr('href')).toContain('https://elsewhere.example.org/css2.css');
10212+
}));
10213+
});
10214+
1018410215
if (!msie || msie >= 11) {
1018510216
describe('iframe[srcdoc]', function() {
1018610217
it('should NOT set iframe contents for untrusted values', inject(function($compile, $rootScope, $sce) {

0 commit comments

Comments
 (0)