Skip to content
Angular ng security 4 min read

Trusting Values Safely

Angular sanitizes untrusted values before binding them into the DOM, which protects you from cross-site scripting (XSS) by default. Occasionally you genuinely need to render content that Angular would otherwise strip — an inline SVG, a blob: URL for a generated file, or markup from a trusted CMS. The DomSanitizer service exposes a family of bypassSecurityTrust* methods that let you tell Angular “I vouch for this value.” Used carelessly, these methods reopen exactly the holes the sanitizer was closing, so they demand discipline.

When bypassing is necessary

Angular’s sanitizer is contextual: it knows the difference between an HTML body, a URL, a style, a resource URL, and a script. For most bindings the sanitizer either accepts a value as-is or quietly removes the dangerous parts and logs a warning. You only need to bypass when both of these are true:

  • The sanitizer is removing content you legitimately need (e.g. an <iframe> src, a data: image URL, or styled HTML).
  • You can prove the value’s origin is trustworthy — it comes from your own code, a vetted backend, or content you fully control, never from raw user input.

If a value originates anywhere near user input, the answer is almost always to sanitize and validate it, not to bypass.

The bypass methods

DomSanitizer provides one method per security context. Each returns a branded Safe* wrapper type that the corresponding binding accepts without further checks.

MethodReturnsUse for
bypassSecurityTrustHtmlSafeHtml[innerHTML] with markup
bypassSecurityTrustStyleSafeStyle[style] values
bypassSecurityTrustScriptSafeScriptinline <script> content (rare)
bypassSecurityTrustUrlSafeUrl[href], [src] on images/anchors
bypassSecurityTrustResourceUrlSafeResourceUrl<iframe src>, <embed>, <object>

The contexts are not interchangeable. A SafeUrl will not satisfy an <iframe src> binding — that requires SafeResourceUrl, the strictest context, because a resource URL loads and executes code.

Trusting an iframe resource URL

A common legitimate case is embedding a video or map whose URL you construct yourself. Inject DomSanitizer and produce a SafeResourceUrl inside a computed signal so it recalculates when the input changes.

import { Component, computed, inject, input } from '@angular/core';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';

@Component({
  selector: 'app-video-embed',
  standalone: true,
  template: `
    @if (safeUrl()) {
      <iframe [src]="safeUrl()" width="640" height="360" allowfullscreen></iframe>
    }
  `,
})
export class VideoEmbedComponent {
  private readonly sanitizer = inject(DomSanitizer);

  /** A YouTube video ID supplied by your own data layer. */
  readonly videoId = input.required<string>();

  readonly safeUrl = computed<SafeResourceUrl>(() => {
    const id = encodeURIComponent(this.videoId());
    const url = `https://www.youtube-nocookie.com/embed/${id}`;
    return this.sanitizer.bypassSecurityTrustResourceUrl(url);
  });
}

Note that the origin is hard-coded and only the opaque videoId is interpolated and URL-encoded. That keeps the trust boundary narrow: an attacker controlling videoId cannot escape the youtube-nocookie.com host.

Trusting HTML from a trusted source

When rendering rich HTML — say, sanitized output from your own backend — you may need bypassSecurityTrustHtml. Prefer letting Angular sanitize first via sanitize() and only bypass the residual:

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

@Pipe({ name: 'trustedHtml', standalone: true })
export class TrustedHtmlPipe implements PipeTransform {
  private readonly sanitizer = inject(DomSanitizer);

  transform(value: string): SafeHtml {
    // Strip dangerous markup first, then mark the cleaned result trusted.
    const clean = this.sanitizer.sanitize(SecurityContext.HTML, value) ?? '';
    return this.sanitizer.bypassSecurityTrustHtml(clean);
  }
}
<article [innerHTML]="article.body | trustedHtml"></article>

If you do not call sanitize() first, bypassSecurityTrustHtml will inject the string verbatim, including any <script> or onerror handlers it contains.

Warning: bypassSecurityTrust* permanently marks a value as safe. The trust travels with the value, so never store a bypassed value where untrusted data could later be concatenated into it. Trust the smallest possible string at the latest possible moment.

What goes wrong when you misuse it

The classic mistake is bypassing a value that contains user input:

// DANGEROUS — never do this.
const html = this.sanitizer.bypassSecurityTrustHtml(userComment);

Output:

<!-- userComment = '<img src=x onerror="fetch(`/api/steal?c=`+document.cookie)">' -->
Browser executes the onerror handler — cookies exfiltrated. Stored XSS.

Because you bypassed the sanitizer, Angular emits no warning and renders the payload. Every user who views the comment runs the attacker’s script.

Best practices

  • Bypass only values whose origin you control end-to-end; treat anything derived from user input as untrusted.
  • Use the narrowest method for the context — never reach for bypassSecurityTrustResourceUrl when a SafeUrl suffices.
  • Hard-code origins and interpolate only opaque, encoded identifiers, as in the iframe example above.
  • Call sanitize(SecurityContext.HTML, value) before bypassSecurityTrustHtml so Angular still strips obvious payloads.
  • Centralize bypassing in a single pipe or service so the trust boundary is auditable, rather than scattering calls across components.
  • Pair bypassing with a strict Content Security Policy as a second line of defense.
  • Add a code-review rule that flags every bypassSecurityTrust* call for explicit justification.
Last updated June 14, 2026
Was this helpful?