Skip to the solution
HardFrontend10 min read

What are security vulnerabilities in Angular apps, and how does DomSanitizer prevent XSS?

1.2k views

The problem

Question presented to candidate: "A security review found that user profiles accept HTML and links. Explain which vulnerabilities matter most in an Angular front end, how Angular protects against XSS by default, and when, if ever, you would use DomSanitizer's bypass methods."

What a strong answer should cover:

  • XSS as the main client-side threat; Angular's model: all bound values are untrusted by default.
  • Contextual sanitization: interpolation escapes text; HTML, URL and resource-URL contexts are sanitized or rejected (unsafe: prefix, stripped <script>/onerror, NG0904 for resource URLs).
  • DomSanitizer: sanitize() for manual cleaning; bypassSecurityTrust* only for values you control, and the risk of using it on user input.
  • Other defences: CSP (including autoCsp and nonces), Trusted Types, AOT and never compiling user templates, XSRF handling in HttpClient, SSR allowedHosts.
  • Practical hygiene: no direct innerHTML via ElementRef, dependency updates, server-side validation.

Clarifying questions expected:

  • "Do users need rich text, or would plain text plus markdown rendered safely do?"
  • "Is the app server-rendered? That adds host-header and request-forgery concerns."

Code / implementation expected: Yes — bindings that show sanitization, and one justified bypassSecurityTrustResourceUrl.

  • security
  • xss

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

10 min read · 9 sections

Read the code

AngularWhat Angular does to untrusted HTML and URLs in bindings
import { Component } from "@angular/core";

// Untrusted input, e.g. a user bio or a link pasted into a comment.
const EVIL_HTML = `<b>Hi</b><img src="x" onerror="alert(1)"><script>alert(2)</script><a href="javascript:alert(3)">me</a>`;

@Component({
  selector: "app-root",
  template: `
    <!-- 1. Interpolation: always text, never parsed as HTML. -->
    <p id="interp">{{ html }}</p>

    <!-- 2. [innerHTML]: parsed, but run through the HTML sanitizer first. -->
    <div id="inner" [innerHTML]="html"></div>

    <!-- 3. [href]: URL context, dangerous schemes are neutralised. -->
    <a id="link" [href]="url">profile</a>
    <a id="safe" [href]="okUrl">docs</a>
  `,
})
export class AppComponent {
  html = EVIL_HTML;
  url = "javascript:alert(4)";
  okUrl = "https://example.com/docs";
}
AngularDomSanitizer by hand: sanitize() and the bypassSecurityTrust methods
import { Component, SecurityContext, inject } from "@angular/core";
import { DomSanitizer, SafeHtml, SafeResourceUrl } from "@angular/platform-browser";

const VIDEO = "https://www.youtube-nocookie.com/embed/abc123";

@Component({
  selector: "app-root",
  template: `
    <div id="trusted" [innerHTML]="trustedHtml"></div>
    <iframe id="video" [src]="videoUrl" title="Demo video"></iframe>
  `,
})
export class AppComponent {
  private sanitizer = inject(DomSanitizer);

  // sanitize(): the same cleaning Angular applies to bindings, called by hand.
  cleaned = {
    html: this.sanitizer.sanitize(SecurityContext.HTML, `<b onclick="steal()">bold</b><script>x()</script>`),
    url: this.sanitizer.sanitize(SecurityContext.URL, "javascript:alert(1)"),
    dataImage: this.sanitizer.sanitize(SecurityContext.URL, "data:image/png;base64,iVBORw0KGgo="),
    style: this.sanitizer.sanitize(SecurityContext.STYLE, "background: url(javascript:alert(1))"),
  };

  // bypassSecurityTrust*(): YOU vouch for the value; Angular stops checking it.
  // Only for values you control, never for user input.
  trustedHtml: SafeHtml = this.sanitizer.bypassSecurityTrustHtml(`<button onclick="track()">Trusted markup</button>`);
  // A resource URL (iframe src, script src) is never sanitized: it must be explicitly trusted.
  videoUrl: SafeResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(VIDEO);
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 80 of 121 in Angular

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Angular questions
Up nextHard

What are Deferrable Views (@defer) and how do they work in Angular 17+?

Continue