Skip to content

Commit 8879aa1

Browse files
committed
feat(security): fail more detectably when using a safe value in an interpolation.
If a user ends up with a safe value in an interpolation context, that's probably a bug. Returning `"SafeValue must use [property]= binding"` will make it easier to detect and correct the situation. Detecting the situation and throwing an error for it could cause performance issues, so we're not doing this at this point (but might revisit later). Part of angular#8511 and angular#9253.
1 parent 44e0ad4 commit 8879aa1

File tree

2 files changed

+38
-12
lines changed

2 files changed

+38
-12
lines changed

modules/@angular/core/test/linker/security_integration_spec.ts

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ class SecuredComponent {
2929
constructor() { this.ctxProp = 'some value'; }
3030
}
3131

32-
function itAsync(msg: string, injections: Function[], f: Function): any; /** TODO #???? */
33-
function itAsync(msg: string, f: (tcb: TestComponentBuilder, atc: AsyncTestCompleter) => void):
34-
any; /** TODO #???? */
32+
function itAsync(msg: string, injections: Function[], f: Function): void;
33+
function itAsync(
34+
msg: string, f: (tcb: TestComponentBuilder, atc: AsyncTestCompleter) => void): void;
3535
function itAsync(
3636
msg: string, f: Function[] | ((tcb: TestComponentBuilder, atc: AsyncTestCompleter) => void),
37-
fn?: Function): any /** TODO #???? */ {
37+
fn?: Function): void {
3838
if (f instanceof Function) {
3939
it(msg, inject([TestComponentBuilder, AsyncTestCompleter], <Function>f));
4040
} else {
@@ -63,8 +63,7 @@ function declareTests({useJit}: {useJit: boolean}) {
6363

6464

6565
itAsync(
66-
'should disallow binding on*',
67-
(tcb: TestComponentBuilder, async: any /** TODO #???? */) => {
66+
'should disallow binding on*', (tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
6867
let tpl = `<div [attr.onclick]="ctxProp"></div>`;
6968
tcb = tcb.overrideView(SecuredComponent, new ViewMetadata({template: tpl}));
7069
PromiseWrapper.catchError(tcb.createAsync(SecuredComponent), (e) => {
@@ -81,7 +80,7 @@ function declareTests({useJit}: {useJit: boolean}) {
8180
itAsync(
8281
'should not escape values marked as trusted',
8382
[TestComponentBuilder, AsyncTestCompleter, DomSanitizationService],
84-
(tcb: TestComponentBuilder, async: any /** TODO #???? */,
83+
(tcb: TestComponentBuilder, async: AsyncTestCompleter,
8584
sanitizer: DomSanitizationService) => {
8685
let tpl = `<a [href]="ctxProp">Link Title</a>`;
8786
tcb.overrideView(SecuredComponent, new ViewMetadata({template: tpl, directives: []}))
@@ -101,7 +100,7 @@ function declareTests({useJit}: {useJit: boolean}) {
101100
itAsync(
102101
'should error when using the wrong trusted value',
103102
[TestComponentBuilder, AsyncTestCompleter, DomSanitizationService],
104-
(tcb: TestComponentBuilder, async: any /** TODO #???? */,
103+
(tcb: TestComponentBuilder, async: AsyncTestCompleter,
105104
sanitizer: DomSanitizationService) => {
106105
let tpl = `<a [href]="ctxProp">Link Title</a>`;
107106
tcb.overrideView(SecuredComponent, new ViewMetadata({template: tpl, directives: []}))
@@ -116,12 +115,32 @@ function declareTests({useJit}: {useJit: boolean}) {
116115
async.done();
117116
});
118117
});
118+
119+
itAsync(
120+
'should warn when using in string interpolation',
121+
[TestComponentBuilder, AsyncTestCompleter, DomSanitizationService],
122+
(tcb: TestComponentBuilder, async: AsyncTestCompleter,
123+
sanitizer: DomSanitizationService) => {
124+
let tpl = `<a href="/foo/{{ctxProp}}">Link Title</a>`;
125+
tcb.overrideView(SecuredComponent, new ViewMetadata({template: tpl, directives: []}))
126+
.createAsync(SecuredComponent)
127+
.then((fixture) => {
128+
let e = fixture.debugElement.children[0].nativeElement;
129+
let trusted = sanitizer.bypassSecurityTrustUrl('bar/baz');
130+
let ci = fixture.debugElement.componentInstance;
131+
ci.ctxProp = trusted;
132+
fixture.detectChanges();
133+
expect(getDOM().getProperty(e, 'href')).toMatch(/SafeValue(%20| )must(%20| )use/);
134+
135+
async.done();
136+
});
137+
});
119138
});
120139

121140
describe('sanitizing', () => {
122141
itAsync(
123142
'should escape unsafe attributes',
124-
(tcb: TestComponentBuilder, async: any /** TODO #???? */) => {
143+
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
125144
let tpl = `<a [href]="ctxProp">Link Title</a>`;
126145
tcb.overrideView(SecuredComponent, new ViewMetadata({template: tpl, directives: []}))
127146
.createAsync(SecuredComponent)
@@ -144,7 +163,7 @@ function declareTests({useJit}: {useJit: boolean}) {
144163

145164
itAsync(
146165
'should escape unsafe style values',
147-
(tcb: TestComponentBuilder, async: any /** TODO #???? */) => {
166+
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
148167
let tpl = `<div [style.background]="ctxProp">Text</div>`;
149168
tcb.overrideView(SecuredComponent, new ViewMetadata({template: tpl, directives: []}))
150169
.createAsync(SecuredComponent)
@@ -169,7 +188,7 @@ function declareTests({useJit}: {useJit: boolean}) {
169188

170189
itAsync(
171190
'should escape unsafe HTML values',
172-
(tcb: TestComponentBuilder, async: any /** TODO #???? */) => {
191+
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
173192
let tpl = `<div [innerHTML]="ctxProp">Text</div>`;
174193
tcb.overrideView(SecuredComponent, new ViewMetadata({template: tpl, directives: []}))
175194
.createAsync(SecuredComponent)

modules/@angular/platform-browser/src/security/dom_sanitization_service.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import {sanitizeUrl} from './url_sanitizer';
99
export {SecurityContext};
1010

1111

12-
/** Marker interface for a value that's safe to use in a particular context. */
12+
/**
13+
* Marker interface for a value that's safe to use in a particular context.
14+
*/
1315
export interface SafeValue {}
1416
/** Marker interface for a value that's safe to use as HTML. */
1517
export interface SafeHtml extends SafeValue {}
@@ -151,7 +153,12 @@ abstract class SafeValueImpl implements SafeValue {
151153
constructor(public changingThisBreaksApplicationSecurity: string) {
152154
// empty
153155
}
156+
154157
abstract getTypeName(): string;
158+
159+
toString() {
160+
return `SafeValue must use [property]=binding: ${this.changingThisBreaksApplicationSecurity}`;
161+
}
155162
}
156163

157164
class SafeHtmlImpl extends SafeValueImpl implements SafeHtml {

0 commit comments

Comments
 (0)