I got super annoyed about this one and ended up... (judge me if you want) doing this...
First of all my needs:
- I am using ngx-markdown but I needed some customization, which involved the need to add some simple style attributes. Since the markdown compiler just gives me back a single 'dumb' HTML string I can't use Angular's
[style.color]="..." or even elementRef.style.color = ...
Angular's sanitizer basically has a white listed set of attributes that are allowed to sail through the sanitizer. style="..." is not one of them. Here are the bulk of them that are allowed (I didn't include ARIA_ATTRS as there are so many).
const URI_ATTRS = tagSet('background,cite,href,itemtype,longdesc,poster,src,xlink:href');
const SRCSET_ATTRS = tagSet('srcset');
const HTML_ATTRS = tagSet('abbr,accesskey,align,alt,autoplay,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,' + 'compact,controls,coords,datetime,default,dir,download,face,headers,height,hidden,hreflang,hspace,' + 'ismap,itemscope,itemprop,kind,label,lang,language,loop,media,muted,nohref,nowrap,open,preload,rel,rev,role,rows,rowspan,rules,' + 'scope,scrolling,shape,size,sizes,span,srclang,start,summary,tabindex,target,title,translate,type,usemap,' +
'valign,value,vspace,width');
const VALID_ATTRS = merge(URI_ATTRS, SRCSET_ATTRS, HTML_ATTRS, ARIA_ATTRS);
So anything here is fine and won't get stripped.
So what did I do? In my custom markdown functions I set itemprop instead of style and then after sanitizing (and it's very important to sanitize any user content to verify there are no 'script' tags etc.) I just replace itemprop= with style=. These styles are all 100% safe from XSS since I made them myself. The alternative of disabling sanitization for all markdown wasn't a good option (since it would allow script through).
// the ngx-markdown internally calls the Angular sanitizer
// this can only be disabled globally, which wasn't an option
let html = this.markdownService.compile(markdown, ....);
// (not shown) I have a custom markdown renderer which adds itemprop= attributes
// https://marked.js.org/using_pro#renderer
// replace itemprop= with style=
const markdown_with_style = encapsulated.replace(/ itemprop=\"/g, ' style="');
// mark the string as bypassed before applying it with innerHTML
return this.sanitizer.sanitize(SecurityContext.HTML, this.sanitizer.bypassSecurityTrustHtml(markdown_with_style));
I'm completely fine with this approach in this limited scope. The only better approach I can think of would be if the Angular sanitizer would let me mark style as safe when sanitizing an HTML string.
Final note: If you can abstract your styles into classes then the class attribute can safely be added - it's only style that is forbidden.