10 Mar 2026
12 min

Security in Angular Applications – What Every Developer Should Know

Security is one of the most important aspects of today’s IT world. At a time when data, know-how, or application source code may be worth millions of dollars, protecting systems is no longer an optional extra, but a necessity.

As Angular developers, do we need to pay special attention to this? Can such a large and advanced framework as Angular do everything for us?

In this article, I will present the most important threats that may affect Angular applications and refer to recent vulnerabilities detected both in Angular itself and in libraries published in the npm ecosystem.

Angular is often regarded as a secure framework out of the box, and it does indeed offer many protection mechanisms. The problem appears when we choose to disable them ourselves.

The Most Common Security Threats in Angular Applications

XSS

Cross-Site Scripting is the most common and the most dangerous attack in SPA applications, and at the same time one that Angular actively tries to prevent. Fortunately, by default our templates are safe. That means bindings and interpolations in templates are automatically encoded.

HTML encoding – what does it mean?

HTML encoding means replacing characters that have special meaning in HTML, such as <, >, or ’, with their safe equivalents in the form of HTML entities. Thanks to this, the browser treats them as plain text rather than code to execute.

For example, the code:

<script>console.error('I caught you!')</script>

will be rendered as:

&lt;script&gt;console.error(&#39;I caught you!&#39;)&lt;/script&gt;

In other words:

  • < is converted to &lt;
  • > is converted to &gt;
  • ’ is converted to &#39;

As a result, the browser displays the text but does not execute the code contained in it.

It is worth clarifying, however, that such encoding occurs only when Angular displays text through interpolation or innerText. In the case of innerHTML, Angular tries to render HTML, so instead of encoding it uses a sanitization mechanism.

The following component shows the difference:

import { Component } from '@angular/core';

@Component({
 selector: 'app-xss-demo',
 template: `
   <h3>Interpolation</h3>
   <div>{{ jsCode }}</div>
   <h3>innerText</h3>
   <div [innerText]="jsCode"></div>
   <h3>innerHTML</h3>
   <div [innerHTML]="jsCode"></div>
 `
 })
export class XssDemoComponent {

readonly jsCode = `<script>console.error('I caught you!')</script>
<img src="x" onerror="alert('XSS')">`;

}

In the first two cases ({{ }} and innerText), Angular applies HTML encoding, so the code is displayed as plain text.

With innerHTML, Angular tries to render the HTML, but first runs sanitization. This means dangerous elements such as <script> or attributes like onerror are removed. In development mode Angular additionally shows a console warning informing you that unsafe content has been removed.

The problem begins when raw HTML appears in the application, for example from a WYSIWYG editor, a product description, or a comment. In such situations, DomSanitizer.bypassSecurityTrustHtml, bypassSecurityTrustStyle, or bypassSecurityTrustUrl are often used unnecessarily.

In practice, these methods disable Angular’s security mechanisms and mean: ‘trust me, I know what I’m doing’. Most often they are used as a quick fix for removed attributes, iframes, or images.

Instead of bypassing security mechanisms, a better approach is to control exactly what can reach the DOM. In practice this means introducing clear rules for allowed HTML elements and attributes. If video embeds are needed, you can allow iframe, but only for selected domains and with a restricted list of safe attributes. In the case of images, it is worth controlling img src and allowing only trusted sources, such as your own CDN or specific domains.

Similarly with addresses: instead of marking every link as trusted using bypassSecurityTrustUrl, it is better to validate URLs by checking the protocol, for example allowing only https:, and optionally restricting domains from which resources may come. Thanks to this, even if content comes from an external source, you still keep control over what resources are loaded and what elements may be rendered in the application.

This approach makes it possible to preserve the functionality of content editors and dynamic HTML, while still maintaining real protection mechanisms against injection of malicious code into the browser.

In Angular, XSS is rarely the framework’s fault. Most often it results from a conscious decision by the developer who only wanted to ‘display some HTML’.

It is worth emphasizing that [innerHTML] itself is not dangerous. Angular sanitizes HTML inserted this way by default.

Sanitization consists of removing or neutralizing dangerous HTML fragments before they are interpreted by the browser. Let us look at a simple Angular component example:

import { Component } from '@angular/core';

@Component({
 selector: 'app-article',
 template: `
  <h2>Article content</h2>
  <div [innerHTML]="content"></div>
`
 })
export class ArticleComponent {
content = `
<p>Great article!</p>
<script>alert('XSS')</script>
`;
}

Before rendering the content, Angular will sanitize it. The HTML:

<p>Great article!</p>
<script>alert('XSS')</script>

will be transformed into a safe form:

<p>Great article!</p>

The <script> tag will be removed and the JavaScript code will not be executed. In addition, Angular’s sanitization mechanism removes event handlers (onclick, onerror), blocks protocols such as javascript: in URLs, and cleans suspicious attributes that could lead to execution of malicious code.

It is worth remembering, however, that Angular does not treat all values the same. The framework distinguishes so-called security contexts, that is, safety contexts in which a given value is used. The context is what determines how Angular sanitizes the value.

  • HTML – when you interpret a value as HTML, for example [innerHTML].
  • Style – when a value is applied to CSS styles, for example [style] or style=””.
  • URL – when you assign an address to properties such as href or src.
  • Resource URL – when the URL points to a resource that can be executed as code, such as an external script or some embedded resources.

This explains why Angular sometimes removes fragments of content that seem harmless at first glance: sanitization is adapted to the specific context in which a value is used.

In practice, this means that instead of bypassing security mechanisms using bypassSecurityTrust…, it is better to control data depending on where it is used. For example:

  • validate URLs instead of marking them as trusted,
  • control img src and allow only safe sources,
  • allow iframe only for selected domains and with a limited list of allowed attributes.

Thanks to this, we keep the functionality of dynamic content without giving up Angular’s built-in security mechanisms.

Tokens and Authorization

Angular can manage the user’s authentication state in an application, but it should be remembered that a frontend application running in the browser is not a secure place to store sensitive data such as long-lived tokens or API keys.

All code and stored data are available on the client side, so we should assume that a potential attacker may gain access to them. For this reason, sensitive operations and secrets should be stored and handled on the backend, while the frontend should operate only on short-lived tokens or session identifiers.

The most common problems are:

  • Access token in localStorage – one successful XSS and the token may be read and used outside the application.
  • Interceptors attaching the token to every request – risk of sending it to external domains or unintended endpoints.
  • Relying only on hiding elements on the frontend and on route guards – the frontend can hide a view, but it should not decide access to data.

There is only one conclusion: every access decision must be confirmed on the server side, otherwise it is only an illusion of security.

What risks are associated with storing a token in the browser?

Choosing where to store a token in the browser always involves certain risks. There is no perfect method, so the final choice should depend on the specific case of each application.

Token in localStorage or sessionStorage

Implementing this solution is very simple. When creating a new project, you have certainly encountered such an approach. Most often, the user/auth state is synchronized with a key in local or session storage. However, this solution is not without flaws. It is highly vulnerable to XSS. The token is stored in plain form, so anyone can inspect and copy it, and therefore use it outside the browser.

Cookies with the HttpOnly flag

In this solution the token is not visible. We cannot see it from the browser level or read it using JavaScript, so the solution is resistant to XSS. However, the cookie is sent automatically with every request, which introduces a CSRF-related risk. Of course, such a cookie must be configured properly. It should be an HttpOnly cookie, inaccessible to JavaScript, and the Secure and SameSite attributes should also be configured correctly.

CSP

Content Security Policy is one of the most powerful protection tools for web applications and at the same time one of the least frequently configured. A properly configured CSP can block the effects of XSS even when a bug already exists in the application code. It restricts where the application may load scripts, styles, and images from, and at the same time forces you to give up dangerous mechanisms such as inline JavaScript or eval. CSP is configured through the HTTP Content-Security-Policy header.

If you have not worked with this mechanism before, it is worth reading the documentation that explains all available directives and how they work in detail:

https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP

In practice, CSP works like a second barrier: even if an attacker injects a <script>, the browser will simply refuse to execute it.

The specifics of CSP in Angular

Angular generates part of its code and styles dynamically. Styles defined in components are injected into the document during application runtime as <style> tags. In practice this means Angular may add additional styles or modify DOM attributes in response to state changes. In development mode, additional mechanisms supporting source maps and Hot Module Replacement are also used.

For this reason, CSP configuration in Angular should take into account the possibility of executing scripts and styles marked with a proper nonce, instead of relying on unsafe-inline, which allows arbitrary JavaScript or CSS to be inserted.

Nonce for scripts and styles

CSP configuration in Angular often relies on the nonce mechanism, that is, a random identifier generated for each request. This identifier is then added both to the CSP header and to the <script> and <style> tags in the HTML document.

An example header may look as follows:

Content-Security-Policy:
 script-src 'self' 'nonce-generatedNonce';
 style-src 'self' 'nonce-generatedNonce';

The nonce must be generated on the server side, injected into the Content-Security-Policy header, and added to the relevant elements in the HTML. In Angular this can be achieved, among other ways, through the CSP_NONCE token or the ngCspNonce attribute.

If you want to better understand why nonce is so important and how strict CSP differs from classic configurations based on domain allowlists, it is worth reading:

https://web.dev/articles/strict-csp

The mechanism works in such a way that the browser executes only those scripts or styles that have a correct nonce attribute matching the value in the CSP header. Thanks to this, even if a vulnerability appears in the application that allows HTML injection, an attacker will not be able to run their own code without knowing the correct nonce value.

Of course, this also requires additional configuration on the production server, for example in Nginx.

Why is this important?

Angular, especially in production mode, may generate or modify styles dynamically. Without nonce, this often ends with the need to enable unsafe-inline, which weakens protection. Nonce allows you to avoid unsafe-inline while still enabling the application to work.

Angular and Trusted Types

Angular also supports Trusted Types enforcement, a browser mechanism that limits the possibility of injecting unsafe strings into APIs such as innerHTML, insertAdjacentHTML, or eval.

In practice, CSP may include:

require-trusted-types-for 'script';
trusted-types angular angular#bundler;

This causes the browser to reject attempts to inject unauthorized HTML or JavaScript even if someone manages to bypass sanitization in the code.

Minimal practical configuration for an Angular SPA

For a new Angular application, it is worth considering at least the following CSP header configuration:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-<dynamic>';
  style-src 'self' 'nonce-<dynamic>';
  img-src 'self' https:;
  connect-src 'self' https://api.yourdomain.com;
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'none';
  require-trusted-types-for 'script';

At first glance this configuration may seem elaborate, but each directive restricts a specific type of resource the browser may load or execute. script-src and style-src control the sources of scripts and styles, img-src defines where images may be loaded from, and connect-src limits the API addresses the application may communicate with. Directives such as object-src, base-uri, or frame-ancestors additionally block less frequently used but potentially dangerous browser mechanisms. Thanks to this, even if a vulnerability appears in the application, the browser may block some attempts to exploit it.

Of course, the exact values depend on the CDNs, analytics tools, or iframes you use. The most important thing is to avoid:

  • ’unsafe-inline’
  • ’unsafe-eval’

Why is CSP the “last line of defense”? Because it works when everything else fails:

  • if an unknown XSS vulnerability appears,
  • if someone uses bypassSecurityTrust*(),
  • if a library introduces a vulnerability,
  • if sanitization turns out to be insufficient.

Recent Angular Vulnerabilities

Although Angular offers many security mechanisms, such as automatic HTML sanitization or protection against XSS, this does not mean that the framework itself guarantees full application security. This is precisely why additional protection mechanisms, such as CSP or proper server-side validation, are so important.

Recent vulnerabilities published by the Angular team show well why it is not worth relying blindly on the framework’s protections. Even mature and widely used tools may contain bugs, especially in less obvious areas such as SVG sanitization, heuristics for determining the origin of HTTP requests, or server-side rendering mechanisms.

In practice, this means that even if an application is written according to good practices and Angular does part of the work for us, there may still be scenarios in which a vulnerability in the framework itself allows its protective mechanisms to be bypassed. In such situations, additional layers of security, for example CSP, may reduce the effects of a potential attack.

Below are examples of several vulnerabilities that have recently been detected in Angular.

SSR: Race Condition – Cross-Request Data Leakage

Source: https://github.com/angular/angular/security/advisories/GHSA-68×2-mx4q-78m7

What was the vulnerability about?

A bug in Angular Server-Side Rendering caused by sharing a global “platform injector”, which created a race condition between parallel requests. If the SSR server rendered two requests at the same time, some data from one could incorrectly end up in the response for the other.

Possible consequences:

  • possible data leakage between users,
  • for example fragments of responses, tokens, or private content may appear in another user’s response,
  • this is not XSS or CORS, but a logical flaw in server-side rendering.

XSRF Token Leakage via URLs

Source: https://github.com/angular/angular/security/advisories/GHSA-58c5-g7wp-6w37

What was the vulnerability about?

Angular’s HttpClient tried to determine whether a request was same-origin based on the presence of a protocol such as http:// or https://. However, URLs beginning with //, so-called protocol-relative URLs, were incorrectly treated as same-origin, so Angular added the X-XSRF-TOKEN header to them.

Possible consequences:

  • the XSRF token was sent even to foreign domains,
  • someone could capture the token and then perform forged POST, DELETE, or PUT requests on behalf of the user — in other words, classic CSRF.

Stored XSS via SVG / MathML Attributes

Source: https://github.com/angular/angular/security/advisories/GHSA-v4hv-rgfq-gp49

What was the vulnerability about?

A bug in the Angular Template Compiler that incorrectly classified some SVG and MathML attributes related to URLs, for example xlink:href, math|href, or attributeName in SVG animation.

Attack mechanism:

  • if the application bound user data to these attributes, for example [attr.xlink:href]=”…”,
  • and the value was malicious, for example a javascript: URL,
  • Angular could fail to sanitize it and allow the URL to be injected.

Result:

Possible Stored XSS, meaning malicious code could execute after a user interaction, such as a click, or automatically through SVG animation.

XSS via Unknown SVG <script> Attributes

Source: https://github.com/angular/angular/security/advisories/GHSA-jrmj-c5cx-3cw6

What was the vulnerability about?

Another bug in Angular sanitization related to the internal security policy: Angular did not classify href and xlink:href in <script> elements inside SVG as contexts requiring Resource URL sanitization.

Possible consequences:

  • if someone used [attr.href] on <svg><script>,
  • Angular could treat the value as plain text,
  • and the attribute could contain, for example, data:text/javascript,…, which leads to JavaScript execution.

This is also XSS, but through a different SVG vector — it differs from the previous vulnerability mainly in which element or attribute was classified incorrectly.

Vulnerabilities in npm

It is also worth mentioning another type of threat that does not concern the application code itself, but external dependencies downloaded from npm. Recently there were serious attacks on the npm ecosystem in which malicious versions of popular packages were published in the registry and could be installed like normal dependencies.

One example is the campaign described in the article “S1ngularity/Nx attackers strike again”, which was part of an attack known as Shai-Hulud – a self-replicating worm operating in the npm ecosystem. Infected packages contained malware capable of stealing tokens, for example from .npmrc files, as well as other confidential environment data such as access tokens, API keys, or environment variables. After obtaining a token, the attackers could publish modified versions of additional libraries in the names of their maintainers. As a result, the infection was not a one-time incident – it spread further through the dependency chain, infecting more packages and projects.

A complete list of affected libraries can be found here:

https://www.aikido.dev/blog/s1ngularity-nx-attackers-strike-again

This kind of supply-chain compromise shows that if the right libraries become infected and you do not monitor the state of your dependencies on an ongoing basis, not only a single application may be at risk, but potentially a huge part of the internet that uses those packages – both in client projects and in developer tools or CI pipelines.

How to Deal with Vulnerabilities

To reduce the risk related to vulnerabilities in frameworks and npm dependencies, actively monitoring security information and reacting quickly to published fixes is essential. In practice, this means regularly monitoring security advisories, such as GitHub Security Advisories, subscribing to announcements from teams maintaining critical libraries, and using tools that automatically analyze project dependencies. The most commonly used solutions include npm audit, GitHub Dependabot, Snyk, Aikido Security, and OWASP Dependency-Check. These tools can detect known vulnerabilities both in direct and transitive dependencies, and often also suggest safe update versions.

Identifying the problem, however, is only half the battle. Equally important is updating packages as quickly as possible after a fix is released, before the vulnerability is widely exploited. A good example is the recent Angular vulnerabilities, which were fixed only starting from version 19.

Angular maintains support only for a limited number of older versions, which means projects using older releases may not receive fixes for newly discovered vulnerabilities. In practice, this means applications that remain on outdated framework versions may be exposed to known security flaws, even if their authors are aware of them.

In such a situation, failing to update is not a neutral technical decision, but a real and growing risk both for the security of the application and for the project as a whole.

Share this post

Sign up for our newsletter

Stay up-to-date with the trends and be a part of a thriving community.