28

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>
Poul Kruijt
  • 69,713
  • 12
  • 145
  • 149
the_critic
  • 12,720
  • 19
  • 67
  • 115
  • @DanielGartmann I actually did want to sanitize the content. I'm surprised about the wording myself now that you point it out, I'll edit my question. Thanks. – the_critic Jun 02 '16 at 20:51
  • __Angular does automatically sanitize content.__ From the _Preventing Cross Site Scripting_ section of https://angular.io/guide/security: *To systematically block XSS bugs, Angular treats all values as untrusted by default. When a value is inserted into the DOM from a template, via property, attribute, style, class binding, or interpolation, Angular sanitizes and escapes untrusted values.* – Sensei James Mar 12 '18 at 01:38

1 Answers1

48

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">';

} 
Poul Kruijt
  • 69,713
  • 12
  • 145
  • 149
  • 2
    oh boy, I tried `inner-html`. Bummer... Thanks! – the_critic Dec 26 '15 at 01:05
  • 3
    The specs have changed since.. alpha.51 if i'm correct. CamelCase everywhere now (except element tags). You are welcome :) – Poul Kruijt Dec 26 '15 at 01:06
  • 1
    I haven't used any alpha version ever, jumped into the beta right away, but still, I think I caught that `inner-html` from some earlier alpha version while doing research. – the_critic Dec 26 '15 at 01:07
  • It seems that it does not watch the changes in the innerHtml for directives. – Cayan Feb 13 '16 at 22:50
  • That's why I said "normal HTML" :) – Poul Kruijt Feb 14 '16 at 09:54
  • 1
    Pert he latest documentation we need to use `DomSanitizer` as seen here https://angular.io/docs/ts/latest/api/platform-browser/index/DomSanitizer-class.html. There has been some renaming. – Jonathan Reyes Oct 02 '16 at 07:00
  • 1
    @JonathanReyes Thanks for the headsup. Updated my answer – Poul Kruijt Oct 03 '16 at 06:13
  • Good solution, tks @PierreDuc, u save my time :) – TDD Oct 14 '16 at 02:55
  • @PierreDuc thx for your answer it was very helpful, do you know how to make dynamic HTML tags with it's own components to work? ,, you mentioned `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.` ,, how to make that to work? thx – Joseph Khella Feb 22 '17 at 11:47
  • @JosephKhella It won't work, unless you dynamically compile components and render them using ViewRefs. In any case, you should not ask a separate question in a comment. See https://stackoverflow.com/questions/34784778/equivalent-of-compile-in-angular-2 – Ruan Mendes Jan 17 '18 at 15:28
  • Awesome stuff. Simple, clean & extremely modular with the Directive approach... – Charles Robertson Apr 28 '18 at 18:55
  • @PierreDuc, thx for the answer. But what if `htmlProperty` contains angular-specific directives such as ``? I found this rendered element is not parsed by Angular. – abbr Nov 26 '18 at 20:11
  • @abbr If you have something like that, you are not using angular the right way. Dynamic templates are frowned upon, and you should find a different way to accomplish the same thing. Either using `*ngIf` or any other structural directive. – Poul Kruijt Nov 27 '18 at 07:28
  • The name of the `@Pipe` (`sanitizeHtml`) is horribly misleading! Please change the name to something like `bypassSecurityTrustHtml` or else it increases the risk of a security problem! – KarolDepka Jun 13 '19 at 00:50
  • 1
    @KarolDepka although I agree with you that the name was poorly chosen (3.5 years ago), there is a warning quote in the answer, and there is also an edit button below the answer, with which you could have proposed a better name. Instead you went for a comment and a downvote. Nevertheless, I've updated the name, to how I have it now in my projects, so thanks for the heads-up – Poul Kruijt Jun 13 '19 at 06:18
  • Missing whitespace `readonlysr` => `readonly sr` in @Directive constructor. Cannot edit (edits must be at least 6 characters). – PierreD Jan 13 '20 at 22:35