Skip to content

Commit fb179d9

Browse files
mprobstnaomiblack
authored andcommitted
docs(security): Add security documentation.
Substantially rewritten, based on original content by Brian Clarkio and Ward Bell and contributions from Naomi Black.
1 parent 9107eee commit fb179d9

18 files changed

+418
-0
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/// <reference path="../_protractor/e2e.d.ts" />
2+
'use strict';
3+
describe('Security E2E Tests', () => {
4+
beforeAll(function() { browser.get(''); });
5+
6+
it('sanitizes innerHTML', () => {
7+
let interpolated = element(By.className('e2e-inner-html-interpolated'));
8+
expect(interpolated.getText())
9+
.toContain('Template <script>alert("0wned")</script> <b>Syntax</b>');
10+
let bound = element(By.className('e2e-inner-html-bound'));
11+
expect(bound.getText()).toContain('Template alert("0wned") Syntax');
12+
let bold = element(By.css('.e2e-inner-html-bound b'));
13+
expect(bold.getText()).toContain('Syntax');
14+
});
15+
16+
it('binds trusted URLs', () => {
17+
let dangerousUrl = element(By.className('e2e-dangerous-url'));
18+
expect(dangerousUrl.getAttribute('href')).toMatch(/^javascript:alert/);
19+
});
20+
21+
it('binds trusted resource URLs', () => {
22+
let iframe = element(By.className('e2e-iframe'));
23+
expect(iframe.getAttribute('src')).toMatch(/^https:\/\/www.youtube.com\//);
24+
});
25+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// #docregion
2+
import { Component } from '@angular/core';
3+
4+
import { BypassSecurityComponent } from './bypass-security.component';
5+
import { InnerHtmlBindingComponent } from './inner-html-binding.component';
6+
7+
@Component({
8+
selector: 'app-root',
9+
template: `
10+
<h1>Security</h1>
11+
<inner-html-binding></inner-html-binding>
12+
<bypass-security></bypass-security>
13+
`,
14+
directives: [
15+
BypassSecurityComponent,
16+
InnerHtmlBindingComponent,
17+
],
18+
})
19+
export class AppComponent {
20+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!--#docregion -->
2+
<h3>Bypass Security Component</h3>
3+
4+
<!--#docregion dangerous-url -->
5+
<h4>A dangerous URL:</h4>
6+
<p><a class="e2e-dangerous-url" [href]="dangerousUrl">Click me.</a></p>
7+
<!--#enddocregion dangerous-url -->
8+
9+
<!--#docregion iframe-videoid -->
10+
<h4>Resource URL:</h4>
11+
<p><label>Showing: <input (input)="updateVideoUrl($event.target.value)"></label></p>
12+
<iframe class="e2e-iframe" width="640" height="390" [src]="videoUrl"></iframe>
13+
<!--#enddocregion iframe-videoid -->
14+
15+
<!--#enddocregion -->
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// #docplaster
2+
// #docregion
3+
import { Component } from '@angular/core';
4+
import { DomSanitizationService, SafeResourceUrl, SafeUrl } from '@angular/platform-browser';
5+
6+
@Component({
7+
selector: 'bypass-security',
8+
templateUrl: 'app/bypass-security.component.html',
9+
})
10+
export class BypassSecurityComponent {
11+
dangerousUrl: SafeUrl;
12+
videoUrl: SafeResourceUrl;
13+
14+
// #docregion trust-url
15+
constructor(private sanitizer: DomSanitizationService) {
16+
// javascript: URLs are dangerous if attacker controlled. Angular sanitizes them in data
17+
// binding, but we can explicitly tell Angular to trust this value:
18+
this.dangerousUrl = sanitizer.bypassSecurityTrustUrl('javascript:alert("Hi there")');
19+
// #enddocregion trust-url
20+
this.updateVideoUrl('PUBnlbjZFAI');
21+
}
22+
23+
// #docregion trust-video-url
24+
updateVideoUrl(id: string) {
25+
// Appending an ID to a YouTube URL is safe.
26+
// Always make sure to construct SafeValue objects as close as possible to the input data, so
27+
// that it's easier to check if the value is safe.
28+
this.videoUrl =
29+
this.sanitizer.bypassSecurityTrustResourceUrl('https://www.youtube.com/embed/' + id);
30+
}
31+
// #enddocregion trust-video-url
32+
}
33+
// #enddocregion
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<!-- #docregion -->
2+
<h3>Binding innerHTML</h3>
3+
<p>Bound value:</p>
4+
<p class="e2e-inner-html-interpolated">{{htmlSnippet}}</p>
5+
<p>Result of binding to innerHTML:</p>
6+
<p class="e2e-inner-html-bound" [innerHTML]="htmlSnippet"></p>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// #docregion
2+
import { Component } from '@angular/core';
3+
4+
@Component({
5+
moduleId: module.id,
6+
selector: 'inner-html-binding',
7+
templateUrl: 'inner-html-binding.component.html',
8+
})
9+
// #docregion inner-html-controller
10+
export class InnerHtmlBindingComponent {
11+
// E.g. a user/attacker controlled value from a URL.
12+
htmlSnippet = 'Template <script>alert("0wned")</script> <b>Syntax</b>';
13+
}
14+
// #enddocregion inner-html-controller
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// #docregion
2+
import { bootstrap } from '@angular/platform-browser-dynamic';
3+
4+
// #docregion import
5+
import { AppComponent } from './app.component';
6+
// #enddocregion import
7+
8+
bootstrap(AppComponent);

public/docs/_examples/security/ts/example-config.json

Whitespace-only changes.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<!DOCTYPE html>
2+
<!-- #docregion -->
3+
<html>
4+
<head>
5+
<title>Angular Content Security</title>
6+
<meta charset="UTF-8">
7+
<meta name="viewport" content="width=device-width, initial-scale=1">
8+
<link rel="stylesheet" href="styles.css">
9+
10+
<!-- Polyfill(s) for older browsers -->
11+
<script src="node_modules/core-js/client/shim.min.js"></script>
12+
13+
<script src="node_modules/zone.js/dist/zone.js"></script>
14+
<script src="node_modules/reflect-metadata/Reflect.js"></script>
15+
<script src="node_modules/systemjs/dist/system.src.js"></script>
16+
17+
<script src="systemjs.config.js"></script>
18+
<script>
19+
System.import('app').catch(function(err){ console.error(err); });
20+
</script>
21+
</head>
22+
23+
<body>
24+
<app-root>Loading...</app-root>
25+
</body>
26+
</html>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"description": "Content Security",
3+
"files": [
4+
"!**/*.d.ts",
5+
"!**/*.js"
6+
],
7+
"tags": ["security"]
8+
}

0 commit comments

Comments
 (0)