Sanitize input in Angular2

the_critic picture the_critic · Dec 25, 2015 · Viewed 35.8k times · Source

I am trying to get third-party (potentially unsafe) html content from my database and insert it into my html document.

How do I safely do that (Protection against XSS) ?

In Angular1.x there used to be $sce to sanitize input, how do I do that in Angular2 ? As far as I understand, Angular2 automatically sanitizes it by default, is that correct ?

Something like this will not work:

<div class="foo">
    {{someBoundValueWithSafeHTML}} // I want HTML from db here
</div>

Answer

Poul Kruijt picture Poul Kruijt · Dec 26, 2015

To insert normal HTML into your angular2 app, you can use the [innerHtml] directive.

<div [innerHtml]="htmlProperty"></div>

This is not going to work with HTML which has its own components and directives, at least not in the way you'd expect it.

If however you do get an unsafe html warning you should trust the HTML first before injecting it. You have to use the DomSanitizer for such a thing. For instance, an <h3> element is considered safe. An <input> element is not.

export class AppComponent  {

    private _htmlProperty: string = '<input type="text" name="name">';

    public get htmlProperty() : SafeHtml {
       return this.sr.bypassSecurityTrustHtml(this._htmlProperty);
    }

    constructor(private sr: DomSanitizer){}
}

And have your template stay the same as this:

<div [innerHtml]="htmlProperty"></div>

A little heads-up though:

WARNING: calling this method with untrusted user data exposes your application to XSS security risks!

If you plan on using this technique more, you can try to write a @Pipe to fulfill this task.

import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';

@Pipe({
    name: 'trustHtml'
})
export class TrustHtmlPipe implements PipeTransform  {    
   constructor(readonly sr: DomSanitizer){}  

   transform(html: string) : SafeHtml {
      return this.sr.bypassSecurityTrustHtml(html); 
   } 
} 

If you have a pipe like this, your AppComponent will change to this. Don't forget to add the pipe to your declarations array of your NgModule:

@Component({
   selector: 'app',
   template: `<div [innerHtml]="htmlProperty | trustHtml"></div>`
})
export class AppComponent{

    public htmlProperty: string = '<input type="text" name="name">';

} 

Or you can write a @Directive to do the same:

@Directive({
   selector: '[trustHtml]'
})
export class SanitizeHtmlDirective {

    @Input()
    public set trustHtml(trustHtml: string) {
      if (this._trustHtml !== trustHtml) {
        this._trustHtml = trustHtml;
        this.innerHtml = this.sr.bypassSecurityTrustHtml(this.trustHtml);
      }
    }

    @HostBinding('innerHtml')
    innerHtml?: SafeHtml;

    private _trustHtml: string;

    constructor(readonly sr: DomSanitizer){}
}

If you have a directive like this, your AppComponent will change to this. Don't forget to add the directive to your declarations array of your NgModule:

@Component({
   selector: 'app',
   template: `<div [trustHtml]="htmlProperty"></div>`
})
export class AppComponent{

    public htmlProperty: string = '<input type="text" name="name">';

}