Feb 22, 2026·6 min read·27 visits
Angular's internal security schema failed to recognize that SVG `<script>` tags can execute code via `href` attributes, treating them as harmless links. This allows XSS via data bindings in SVG templates. Patch immediately to versions 19.2.18, 20.3.16, 21.0.7, or 21.1.0-rc.0.
A high-severity Cross-Site Scripting (XSS) vulnerability in the Angular Template Compiler allows attackers to bypass Angular's strict sanitization mechanisms. By leveraging SVG-specific attributes like `href` and `xlink:href` on `<script>` elements, attackers can inject malicious executable code that the framework mistakenly categorizes as safe URLs. This affects major versions of Angular Core, including v19, v20, and v21.
Angular is famous—or infamous, depending on who you ask—for being paranoid. It is the helicopter parent of JavaScript frameworks. It holds your hand, checks your inputs, and aggressively sanitizes anything that looks even remotely like a script tag. If you try to bind a string containing <script>alert(1)</script> to [innerHTML], Angular silently nukes it from orbit. This is why we rarely see XSS in Angular applications compared to the Wild West of raw React or jQuery.
But even the most vigilant guards have blind spots. In this case, the blind spot wasn't in the HTML specification, but in the quirky, legacy-ridden world of Scalable Vector Graphics (SVG). While Angular was busy meticulously guarding the front door (<script src="...">), it left the window (<svg><script href="...">) unlatched.
CVE-2026-22610 is a failure of categorization. It’s not that Angular couldn't sanitize the input; it’s that it didn't think it needed to. The framework looked at an SVG script tag with an href attribute, shrugged, and said, "That looks like a hyperlink to me." That assumption turned a benign property binding into a high-severity Remote Code Execution vector within the victim's browser context.
To understand this bug, you have to understand Angular's DOM Security Schema. Angular classifies every DOM property into security contexts: HTML, STYLE, URL, and the most dangerous of all, RESOURCE_URL.
A URL context is for things like <a href="...">. You can put a link to https://google.com, but Angular will strip out javascript:alert(1).
A RESOURCE_URL context is for things that load executable code, like <script src="..."> or <iframe src="...">. Angular locks this down tight. You cannot bind a dynamic value to a RESOURCE_URL unless you explicitly tell Angular, "I trust this value with my life," using bypassSecurityTrustResourceUrl.
Here lies the logic error. Angular correctly identified that <script src="..."> requires RESOURCE_URL security. However, in the world of SVG, you don't always use src. Historically, you used xlink:href, and in modern SVG 2 specs, you just use href.
Angular's internal schema map missed this nuance. When the compiler encountered <script [attr.href]="..."> inside an SVG, it checked its list of "dangerous" attributes. Seeing href but failing to associate it with the script tag in an executable context, it downgraded the security requirement from RESOURCE_URL to plain old URL. This downgrade is fatal. It means the sanitizer allows data: URIs, which are perfectly valid for images or links but devastating when fed to a script tag.
Let's look at the actual code in packages/core/src/sanitization/sanitization.ts. This is where the decision to sanitize—or not to sanitize—is made.
Before the Patch:
The function getUrlSanitizer determines which sanitizer function to use based on the tag and property name. Notice the omission in the HREF_RESOURCE_TAGS set.
// The set of tags where 'href' is considered a loadable resource
const HREF_RESOURCE_TAGS = new Set(['base', 'link']);
export function getUrlSanitizer(tag: string, prop: string) {
// If it's a 'src' attribute on a known tag, sanitize as Resource URL
if (prop === 'src' && SRC_RESOURCE_TAGS.has(tag)) {
return ɵɵsanitizeResourceUrl;
}
// If it's an 'href' attribute on 'base' or 'link', sanitize as Resource URL
if (prop === 'href' && HREF_RESOURCE_TAGS.has(tag)) {
return ɵɵsanitizeResourceUrl;
}
// Fallback: Just a regular URL (allows data: URIs!)
return ɵɵsanitizeUrl;
}Because script was missing from HREF_RESOURCE_TAGS, the function returned ɵɵsanitizeUrl. This sanitizer allows data: protocols because they are safe for <img> or <a> tags.
After the Patch (Commit 91dc91bae4):
The Angular team realized that script tags also use href (and xlink:href) to load code.
// 'script' is added to the naughty list
const HREF_RESOURCE_TAGS = new Set(['base', 'link', 'script']);
export function getUrlSanitizer(tag: string, prop: string) {
const isResource =
(prop === 'src' && SRC_RESOURCE_TAGS.has(tag)) ||
(prop === 'href' && HREF_RESOURCE_TAGS.has(tag)) ||
// Explicit check for the legacy SVG attribute
(prop === 'xlink:href' && tag === 'script');
return isResource ? ɵɵsanitizeResourceUrl : ɵɵsanitizeUrl;
}By forcing ɵɵsanitizeResourceUrl, Angular now throws an error if a developer tries to bind a string to these attributes without explicit trust, effectively killing the XSS vector.
Exploiting this requires a specific but not uncommon scenario: an Angular application that renders SVGs using templates where attributes are bound to user-controllable data.
Imagine a dashboard application that allows users to customize their profile widget with an "Avatar" SVG. The application code might look like this:
<!-- component.html -->
<svg width="100" height="100">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
<!-- The developer thinks they are binding a link to a profile script -->
<script [attr.href]="userProfileUrl"></script>
</svg>The developer assumes userProfileUrl is just a URL. Angular cleans URLs, right?
The Attack Chain:
Injection: The attacker updates their profile settings. In the userProfileUrl field, instead of https://example.com/profile.js, they input a Data URI payload:
data:text/javascript,alert(document.cookie)
Rendering: The Angular compiler sees [attr.href] on a script tag. It checks its schema. It thinks, "This is a safe URL context." It validates the protocol. data: is allowed in safe URL contexts (e.g., <img src="data:...">).
Execution: Angular renders the DOM:
<script href="data:text/javascript,alert(document.cookie)"></script>The browser's SVG engine parses this, sees a script element with an executable href, and immediately executes the JavaScript inside the Data URI.
Boom. You have XSS. No complex bypasses, no polyglots. Just a simple logic gap in the framework.
This vulnerability is particularly dangerous because it subverts the expectations of developers. Angular developers are trained to believe that the framework handles context-aware escaping automatically. They rely on the compiler to yell at them if they do something dangerous.
With a CVSS score of 8.5, the impact is high. Successful exploitation allows for:
Furthermore, because this is a client-side template injection issue, it can often bypass server-side WAFs that aren't looking for standard XSS payloads but might miss a data:text/javascript string hidden inside a JSON API response.
The mitigation is straightforward: upgrade. The Angular team has backported fixes to all supported active versions.
Patched Versions:
If you are stuck on an unpatched version (and shame on you, it's 2026), you have two temporary options:
data: in the script-src directive will prevent the browser from executing the payload, even if Angular renders it.
Content-Security-Policy: script-src 'self'; object-src 'none';[attr.href] and [attr.xlink:href] inside <svg> blocks. If you find them on <script> tags, remove them or hardcode the values.CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
@angular/core Angular | < 19.2.18 | 19.2.18 |
@angular/core Angular | >= 20.0.0-next.0, < 20.3.16 | 20.3.16 |
@angular/core Angular | >= 21.0.0-next.0, < 21.0.7 | 21.0.7 |
@angular/core Angular | >= 21.1.0-next.0, < 21.1.0-rc.0 | 21.1.0-rc.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 (Improper Neutralization of Input During Web Page Generation) |
| CVSS v4.0 | 8.5 (High) |
| Attack Vector | Network |
| Attack Complexity | Low |
| Privileges Required | Low |
| Impact | High (Confidentiality, Integrity, Availability) |
| Exploit Status | PoC Available |
The software does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.