Displayed data and template code
Angular treats received values as untrusted by default but treats templates as trusted application code. The example’s interpolation presents the string as text; the strong tag remains text in that first view. This differs from concatenating user input with Angular syntax and compiling the result.
Under that change, content has already crossed the data-to-code boundary and binding sanitization does not fix the cause. Keep templates under application control and supply variable content through appropriate bindings.
Context determines protection
A string is not safe for every destination merely because it was accepted in one. HTML, Style, URL and Resource URL have different contracts. The innerHTML binding interprets HTML and Angular sanitizes untrusted values in that context.
A navigation URL differs from an address loading executable content such as iframe src. An arbitrary Resource URL cannot be made safe merely by removing a few characters. Decisions must consider permitted origin, loaded content and exact context. HTML approval does not approve executing a resource.
Sanitizing and declaring trust are different operations
DomSanitizer.sanitize with SecurityContext.HTML and a plain string applies content sanitization. bypassSecurityTrustHtml instead marks the value trusted and bypasses that context’s protection. Such a declaration requires analysis of value origin and construction; it is not an automatic fix for an error message.
The sanitize API can also unwrap an already-marked SafeValue, so previously assigned trust remains relevant. For other contexts, passing a plain string to this explicit API can throw under its documented contract. Do not generalize HTML semantics to every destination.
DOM APIs and external integrations
ElementRef provides access to an element, but that reference does not automatically sanitize every operation performed on it. Direct innerHTML writes or passing a string to a DOM-manipulating library can avoid the Angular binding path. Prefer the template contract when suitable; when integration requires direct access, define destination-compatible sanitization and review later transformations.
Changing the type to any or adding an assertion modifies neither content nor how the browser interprets it.
Authorization also belongs on the server
A guard can prevent normal navigation to a page or direct users elsewhere. It does not prevent client code modification or a request being sent directly to the API. The server must check identity, operation and requested resource.
Hiding a button or disabling a field can improve interface flow but does not establish that permission. In a diagnosis scenario, an accepted response for an unauthorized operation should lead to backend checks even when guards and visual classes appear correct.
XSRF requires both sides of the contract
HttpClient XSRF protection implements the client side of a token scheme. By default it reads XSRF-TOKEN and uses X-XSRF-TOKEN on eligible mutating requests to relative or same-origin URLs; GET and HEAD do not receive that treatment. The backend must issue the token and verify requests under the agreed policy.
Without that, HttpClient presence does not complete protection. When names differ, withXsrfConfiguration aligns cookieName and headerName. Disabling the mechanism does not resolve missing server verification.
CSP, Trusted Types and delivery review
CSP and Trusted Types add browser- and DOM-level protection, complementing the earlier contracts. Under nonce-based CSP, the server generates a random value per response and the value supplied to Angular through ngCspNonce or CSP_NONCE must match that response’s header.
Do not copy a fixed example nonce into every response. Use AOT in delivery according to Angular guidance and avoid compiling templates built from external data. These measures do not replace authorization or review of existing application bypass APIs.
// Reading example: two Angular rendering contexts; no storage or API.
import {Component, signal} from '@angular/core';
@Component({
selector: 'comment-preview', standalone: true,
template: `<label>Comment
<textarea #entry [value]="comment()" (input)="comment.set(entry.value)"></textarea>
</label>
<h2>Text preview</h2><p>{{ comment() }}</p>
<h2>HTML binding preview</h2><div [innerHTML]="comment()"></div>`,
})
export class CommentPreview {
readonly comment = signal('A <strong>draft</strong> note');
}Interpolation presents string characters as text. The innerHTML binding requests HTML interpretation and uses Angular protection for that context. The example calls no bypassSecurityTrustHtml and writes nothing directly through nativeElement.
This preview does not define a complete comment-publication policy, permissions, external links or third-party content.
Common pitfalls
Treating bypass as sanitization; trusting all API content; compiling user data as a template; relying only on a guard to protect an API; enabling client XSRF without a backend contract; reusing a fixed CSP nonce.
Related topics: HTTP, streams and response acceptance · Routing, state and navigation decisions · Rendering, hydration and release evidence
Security depends on the specific boundary: data to DOM, data to template, client to server and code to execution policy. Keep untrusted data as data, use destination-appropriate protection and require separate evidence for authorization and remote acceptance.
Reference: Untrusted data and trusted templates · Angular 22; standalone components