Angular blocks most cross-site scripting by default, so the vulnerabilities that reach production in Angular applications are almost always places where a developer stepped around the framework. Interpolation is escaped, bound HTML is sanitised and templates are compiled ahead of time, yet teams still ship bypassSecurityTrustHtml on user content, raw ElementRef DOM writes, nonce-less Content Security Policies and server-side rendering endpoints that trust any Host header. This guide explains what Angular protects automatically, where that protection stops, and how to add Trusted Types, CSP, XSRF handling, SSR host validation and update discipline on top. It is written for Angular developers, application security engineers and reviewers who assess Angular code. For the general theory of XSS, see the Cross-Site Scripting (XSS) Prevention Guide; this page stays inside Angular.
Key takeaways
- Trust the template, distrust the escape hatches. Interpolation is always escaped and HTML, style and URL bindings are sanitised; every
bypassSecurityTrust*call,ElementRef.nativeElementwrite and directdocumentaccess is where review effort belongs. - Resource URLs are never sanitised. Angular cannot make a
<script src>or<iframe src>value safe, so those values must come from an allowlist you control. - Enforce Trusted Types and a nonce or hash based CSP. Angular ships named Trusted Types policies and two CSP mechanisms (
ngCspNonce/CSP_NONCEandsecurity.autoCsp) that turn a missed sink into a blocked one. - XSRF and SSR protections are half built.
HttpClientsends theX-XSRF-TOKENheader but the server must issue and verify it; Angular SSR rejects unknown hosts only onceallowedHostsis configured. - Stay on a supported major. Angular majors get 12 months of active support and 12 months of LTS; AngularJS support ended in January 2022 and receives no fixes from the Angular team.
What Angular protects automatically
Angular treats every value bound into the DOM as untrusted. The protection depends on the security context the binding lands in, and knowing those four contexts is the basis of every Angular code review.
| Security context | Typical bindings | What Angular does | Residual risk |
|---|---|---|---|
| Text (interpolation) | {{ value }}, [textContent] | Always escaped; rendered as text | None from the framework; risk returns if the same data is later used in another context |
| HTML | [innerHTML] | Sanitised: script elements, event handler attributes and dangerous URLs are stripped | Markup the sanitiser keeps (forms, links, images) can still support phishing or layout abuse |
| Style | [style], [style.background] | Sanitised | User-controlled styles can still overlay or hide page elements |
| URL | [href], [src] on img | Sanitised: unsafe schemes such as javascript: are neutralised | A safe URL can still point to a hostile destination (open redirect, phishing) |
| Resource URL | <script src>, <iframe src>, <embed> | Not sanitised: Angular throws unless the value is explicitly trusted | Anything passed through bypassSecurityTrustResourceUrl executes as code or a full document |
When the HTML sanitiser removes content, Angular logs a development warning that it stripped some content. Treat that warning as a signal: either the input really was hostile, or a developer is about to "fix" it with a bypass. Templates themselves are compiled ahead of time (AOT), which is the Angular CLI default. Angular's documentation describes compiling templates at runtime with the JIT compiler, especially with user data, as a security anti-pattern, because a template is code. The same applies to generating Angular templates on the server with another templating language: escaped server output can still contain Angular template syntax that the client then evaluates.
The escape hatches and how to review them
Most real Angular XSS findings sit in a short list of APIs. A reviewer who greps for these names covers the bulk of the framework-specific attack surface.
| API or pattern | Why it is dangerous | Acceptable use | Safer alternative |
|---|---|---|---|
DomSanitizer.bypassSecurityTrustHtml | Disables the HTML sanitiser for that value | Static, developer-authored markup, or HTML already cleaned server side with a maintained sanitiser | Plain [innerHTML] binding, or a component that renders structured data |
bypassSecurityTrustUrl | Allows schemes the sanitiser would block | Known custom schemes from configuration (for example a desktop deep link) | Validate scheme and host against an allowlist, then bind normally |
bypassSecurityTrustResourceUrl | Loads arbitrary script, iframe or object sources | A URL built from a fixed origin plus a validated identifier | Map identifiers to URLs in code; never pass user strings through |
bypassSecurityTrustScript, bypassSecurityTrustStyle | Executes or applies untrusted code | Rarely justified | Component logic and class bindings |
ElementRef.nativeElement.innerHTML, document.write, insertAdjacentHTML | Bypasses Angular entirely; no sanitisation | Almost never | Template bindings, or DomSanitizer.sanitize(SecurityContext.HTML, value) before the write |
| Third-party libraries that touch the DOM (charting, rich text, maps) | Their own sinks are outside Angular's sanitiser | When the library documents its escaping | Pass text, not HTML, into library options; review the library's escaping options |
JIT compilation, platform-browser-dynamic with runtime templates | Template injection becomes code execution | Legacy builds being migrated | AOT for every production build |
- Make bypasses visible: wrap the sanitiser bypass in one small, well-reviewed service (for example a
TrustedContentService) and ban direct calls elsewhere with a lint rule. The service is then the single place to require a sanitiser such as DOMPurify (DOMPurify.sanitize()) before trusting rich text. - Prefer sanitising over trusting: if a feature needs user-authored rich text, bind the cleaned value through
[innerHTML]so Angular's sanitiser still runs as a second layer, rather than marking it trusted. - Treat URLs as data with a destination: Angular removes
javascript:, but only an allowlist stops an attacker-chosen domain. See the Input Validation and Output Encoding Guide.
Trusted Types and Content Security Policy
Trusted Types makes the browser refuse to assign plain strings to dangerous DOM sinks unless they pass through a named policy. Angular supports it directly, which means a forgotten nativeElement.innerHTML in your code or a dependency fails loudly instead of executing.
| Trusted Types policy name | Required when | Review note |
|---|---|---|
angular | Always, for Angular to work under enforcement | Baseline; safe to allow |
angular#bundler | The CLI bundler creates lazy-loaded chunk files | Normal for CLI builds with lazy routes |
angular#unsafe-bypass | The application calls any bypassSecurityTrust* method | Needing it is a finding to justify; aim to remove it |
angular#unsafe-jit | The JIT compiler or platform-browser-dynamic is used | Should not appear in production |
angular#unsafe-upgrade | Hybrid AngularJS applications using @angular/upgrade | Temporary, tied to the migration plan |
A minimal enforcing header is Content-Security-Policy: trusted-types angular; require-trusted-types-for 'script';, extended with only the policies your build genuinely needs. Roll it out with Content-Security-Policy-Report-Only first so violations are reported without breaking users.
For script control, Angular offers two mechanisms:
- Nonce per request: set the
ngCspNonceattribute on the root element when the server templatesindex.html, or provide theCSP_NONCEinjection token when the nonce is only known at runtime. Angular's documented minimal policy isdefault-src 'self'; style-src 'self' 'nonce-...'; script-src 'self' 'nonce-...';. The nonce must be unique and unpredictable for every response; a CDN that caches HTML will replay one nonce to every visitor, so generate it at the edge or do not cache the document. - Hashes at build time: setting
security.autoCsptotruein the workspace configuration makes the CLI hash the inline scripts inindex.htmland add a CSP<meta>tag using'strict-dynamic',object-src 'none'andbase-uri 'self'. It suits static hosting and cacheable HTML, covers scripts only (configurestyle-srcyourself) and cannot be used with server-side rendering. - Directives a meta tag cannot carry:
frame-ancestorsand violation reporting need a real response header, so set them at the server or CDN even whenautoCsphandles scripts. See the Content Security Policy Guide and the Clickjacking Guide.
HttpClient: XSRF, XSSI and token handling
HttpClient provides the client half of two classic protections, and both fail silently if the server half is missing.
- XSRF: the built-in interceptor reads the
XSRF-TOKENcookie and sends its value as theX-XSRF-TOKENheader on mutating requests (POST, PUT, PATCH, DELETE) to relative or same-origin URLs. It does nothing for GET or HEAD, or for absolute cross-origin URLs. The backend must set a random token in that cookie and reject requests where the header does not match. Rename them withwithXsrfConfiguration({ cookieName, headerName });withNoXsrfProtection()turns the feature off and should appear only where the API uses a different, documented defence. See the CSRF Prevention Guide. - XSSI: servers can prefix JSON responses with
)]}',followed by a newline so the response cannot be executed as a script by another site;HttpClientstrips the prefix before parsing. - Bearer tokens in interceptors: an interceptor that adds
Authorizationto every request will hand the token to any third-party URL the application calls. Match the request origin against an allowlist before attaching credentials, and avoid storing long-lived tokens inlocalStorage, where any XSS can read them. See the Token and Session Security Guide. - Route guards are not access control:
canActivateand similar guards decide what the browser shows. Every API behind them must enforce authorisation on the server, because a user can call the API directly.
Server-side rendering and hydration
Angular SSR runs your application code on a Node.js (or edge) server for every request, which changes the threat model: an injection bug can now reach server memory, internal networks and environment variables, not just one browser.
- Host header validation: SSR builds absolute URLs from request headers, which an attacker can use for server-side request forgery. Configure
allowedHostsundersecurityin the@angular/build:applicationbuilder options, or pass it toAngularAppEngine/AngularNodeAppEngine; the Node variant also readsNG_ALLOWED_HOSTS. Requests for unrecognised hostnames receive400 Bad Request. - Proxy headers:
Forwardedand allX-Forwarded-*headers are treated as untrusted and stripped unless you list them intrustProxyHeaders(orNG_TRUST_PROXY_HEADERS). Only trust them when a reverse proxy you control overwrites them.X-Forwarded-Protomust behttporhttpsandX-Forwarded-Portmust be numeric. See the SSRF Prevention Guide. - Server-side XSS: HTML produced outside Angular on the server (error pages, Express middleware) needs an auto-escaping template engine.
- State transfer: data passed from server to client for hydration is embedded in the page. Never place secrets, internal URLs or other users' data in it, and keep per-request data scoped to the request rather than in module-level variables shared across requests.
- Secrets and environments: anything in
environment.tsor bundled configuration is shipped to every browser. API keys there are public. Server-only credentials belong in server environment variables read by the SSR process, never imported by shared code.
Build, dependencies and supply chain
An Angular application commonly pulls hundreds of transitive npm packages into its build, and any of them can run code at install time or ship code into the bundle that runs with your origin's privileges.
- Lock and verify: commit
package-lock.jsonand usenpm ciin CI so builds install exactly what was reviewed. - Delay and review updates: configure a cooldown on automated dependency updates and require human review for new dependencies and install scripts. See the Malicious Package and Dependency Confusion Guide.
- Scan continuously: run
npm auditor a software composition analysis tool in CI, and generate an SBOM per release. See the SBOM Guide. - Pin third-party scripts: self-host scripts where possible; for any CDN script, pin an exact version and add Subresource Integrity (
integritypluscrossorigin). - Keep production builds clean: use AOT and production configuration, review whether source maps are published, and keep the development server (
ng serve) bound to localhost; it is not a production server. - Protect publishing credentials: if you publish Angular libraries, npm tokens are high-value non-human identities. See the npm Tokens Security Guide.
Versions, support and migration
| Line | Status (Angular release page, October 2026) | Security fixes | Action |
|---|---|---|---|
| Angular 22 | Active, released 3 June 2026; LTS to June 2028 | Yes | Target for new work |
| Angular 21 | LTS until June 2027 | Critical and security fixes only | Plan upgrade within the LTS window |
| Angular 20 | LTS until 28 November 2026 | Critical and security fixes only, ending soon | Upgrade now |
| Angular 2 to 19 | Unsupported | No | Treat as a vulnerability finding |
| AngularJS 1.x | Support ended January 2022 | None from the Angular team | Migrate, or isolate and compensate with CSP, WAF rules and reduced exposure |
Angular now releases a major roughly every 12 months, with 12 months of active support followed by 12 months of LTS in which only critical fixes and security patches ship. ng update moves one major at a time, so a team three majors behind faces three migrations; budgeting one upgrade per year is cheaper than a forced multi-version jump after a disclosure. Angular's documentation also warns against private, customised forks of the framework because they fall behind on security fixes.
Implementation roadmap
- Inventory (week 1): list Angular and AngularJS applications with their versions, SSR use and hosting model. Grep for
bypassSecurityTrust,nativeElement,innerHTML,document.,withNoXsrfProtectionand JIT imports. - Fix the sinks: replace each bypass with a binding or a sanitised value, and centralise any that must remain in one reviewed service.
- Deploy CSP in report-only: choose nonce (
ngCspNonce/CSP_NONCE) for SSR or templated HTML,autoCspfor static hosting. Collect reports for two to four weeks. - Add Trusted Types in report-only: start with
trusted-types angular angular#bundlerand fix violations rather than adding unsafe policies. - Enforce in rings: internal users, then a percentage of traffic, then everyone. Roll back by switching the header back to report-only, which needs no code release.
- Close the server half: confirm the API issues and verifies XSRF tokens, configure
allowedHostsandtrustProxyHeadersfor SSR, and add authorisation checks behind every guarded route. - Make it stick: lint rules that block new bypasses, dependency cooldown and review, and a yearly upgrade slot in the roadmap.
Detection and measurement
- CSP and Trusted Types reports: send violation reports to a collector and alert on new sinks or new script sources after a release.
- SSR logs: alert on spikes of
400responses from host validation and on invalid header errors, which indicate probing. - API logs: XSRF rejections and authorisation failures on routes the UI guards.
| Metric | Target |
|---|---|
Number of bypassSecurityTrust* call sites outside the central service | Zero |
| Production applications serving an enforced CSP with nonce or hashes | 100% |
Applications enforcing Trusted Types without angular#unsafe-* policies | Rising quarter on quarter |
| Applications on a supported Angular major | 100%, AngularJS on a dated exit plan |
| Median days from a critical npm advisory to deployed fix | Within your documented remediation window (ASVS V15.1.1) |
SSR deployments with allowedHosts configured | 100% |
Common failure patterns
- Bypass to silence the warning: the sanitiser strips a user's formatting, a developer wraps the value in
bypassSecurityTrustHtml, and stored XSS ships. Sanitise with a library and keep the Angular sanitiser in the path. - Direct DOM access in "just one" directive: a tooltip or highlighting directive writes
innerHTMLthroughElementRef, outside every framework protection. - Static nonce behind a CDN: a nonce baked into cached HTML is the same for every visitor and defeats the policy.
- Assuming XSRF is on end to end: the client sends
X-XSRF-TOKEN, but the API never checks it, or the API is on another origin where the interceptor never adds it. - Floating CDN versions: a script tag on
@latestwithout SRI pulls whatever is published next. In the Lottie Player npm compromise, sites loading the player from public CDNs with the latest tag served a wallet-draining prompt after a stolen token published malicious versions; pinning and SRI would have kept them on the clean release. - Stranded on AngularJS: an internal portal on AngularJS with no fixes since 2022, still exposed to the internet.
Framework mapping
| Control area | OWASP ASVS 5.0 | NIST SP 800-53 Rev 5 | NIST CSF 2.0 / ATT&CK |
|---|---|---|---|
| Context-aware encoding, sanitiser discipline | V1.2.1, V1.3.1, V3.2.2 | SI-10, SI-15 | PR.PS-06; mitigates T1059.007 JavaScript |
| CSP, Trusted Types, frame-ancestors | V3.4.3, V3.4.6 | SI-10 | PR.PS-06; T1189 Drive-by Compromise |
| External scripts with SRI | V3.6.1 | SR-3 | T1195.002 Compromise Software Supply Chain |
| Dependency update windows | V15.1.1, V15.2.1 | RA-5, SA-11 | ID.RA-01 |
| Session token exposure | V14.2.1 | SA-15 | T1539 Steal Web Session Cookie |
Practitioner checklist
- Grep every Angular repository for
bypassSecurityTrust,nativeElementandinnerHTML, and justify or remove each hit. - Confirm all production builds are AOT and no runtime template compilation ships.
- Never bind user data to resource URL contexts; map identifiers to fixed URLs in code.
- Deploy a nonce or hash based CSP (
ngCspNonce,CSP_NONCEorsecurity.autoCsp) via report-only first. - Enforce Trusted Types with the minimum Angular policy set.
- Verify the backend issues and checks
XSRF-TOKEN/X-XSRF-TOKEN, and that interceptors only attach credentials to approved origins. - Configure
allowedHostsandtrustProxyHeaderson every SSR deployment. - Remove secrets from
environment.tsand anything else bundled for the browser. - Pin dependencies, add update cooldowns, and use SRI on any CDN script.
- Keep every application on a supported Angular major and set a dated exit plan for AngularJS.
Standards and references
- Angular: Security
- Angular: Versioning and releases
- Angular: Server and hybrid rendering
- AngularJS: Version support status
- OWASP: Application Security Verification Standard 5.0
- OWASP: Cross Site Scripting Prevention Cheat Sheet
- OWASP: Content Security Policy Cheat Sheet
- Cure53: DOMPurify
Related NHI Mgmt Group resources: Cross-Site Scripting (XSS) Prevention Guide · Content Security Policy Guide · Vue Security Guide · React and Next.js Security Guide · Client-Side Security Guide