Summary A discrepancy between WHATWG URL parsing and Angular SSR's URL resolution allows attackers to bypass same-origin checks and cause Server-Side Request Forgery (SSRF), potentially leaking sensitive server-side credentials.
Technical Description When applications validate incoming URLs using the WHATWG URL standard (new URL(input, trustedOrigin)), Unicode whitespace characters (such as NO-BREAK SPACE U+00A0 or ZERO WIDTH NO-BREAK SPACE U+FEFF) are not stripped and are evaluated as part of a same-origin relative path (e.g. http://trusted-origin/%C2%A0//attacker.example/collect). Consequently, these URLs successfully pass application-level same-origin checks.
However, @angular/platform-server's URL resolution utility (resolveUrl / parseUrl) previously executed String.prototype.trim(). Because JavaScript's String.prototype.trim() strips all Unicode whitespace (including U+00A0), the leading non-breaking space was removed, converting the string into a cross-origin protocol-relative URL (//attacker.example/collect). When resolved during server-side rendering (such as in relativeUrlsTransformerInterceptorFn), this caused the HTTP request to be dispatched to the attacker-controlled origin (http://attacker.example/collect), leaking any credentials (such as Authorization headers) attached by the application for the intended same-origin request.
Impact & Reachability Reachability: The vulnerability affects Angular Server-Side Rendering (SSR) applications where user-controlled input influences resource or request URLs processed by Angular's HttpClient, an application-level same-origin check is performed before dispatching, and sensitive server-side credentials (such as API keys or Bearer tokens) are attached to approved requests. Impact: Successful exploitation allows attackers to bypass same-origin validation, triggering Server-Side Request Forgery (SSRF) and leaking sensitive server-side credentials attached to the request.
Proof of Concept: ts // Interceptor performing same-origin validation const trustedOrigin = new URL('http://localhost:4000/'); const target = new URL(req.urlWithParams, trustedOrigin);
if (target.origin !== trustedOrigin.origin) { throw new Error('Cross-origin request blocked'); }
// Request passes validation, server attaches sensitive credential: const authenticatedReq = req.clone({ headers: req.headers.set('Authorization', 'Bearer SERVER-SECRET-TOKEN'), });
// @angular/platform-server previously trimmed the URL, converting it into // //attacker.example/collect and routing the credential to the attacker.
Workarounds Validate and sanitize input URLs to disallow leading Unicode whitespace characters (such as \u00A0) before performing origin checks or passing them to HttpClient. Avoid relying solely on new URL(input, trustedOrigin).origin for authorization if the input string may be trimmed or processed by utilities that normalize whitespace differently from the WHATWG URL standard.
Angular automatically sanitizes untrusted values bound to security-sensitive DOM sinks (such as href, src, action, xlink:href, and data) to protect against Cross-Site Scripting (XSS).
Prior to the fix, the Angular compiler determined the SecurityContext for directive host bindings (host: {'[attr.href]': 'value'} or @HostBinding('attr.href')) based solely on the declaring directive or component selector at compile time, rather than the concrete host element that the directive was applied to.
When a directive with a security-sensitive host binding was applied to a different concrete host element—such as through: - hostDirectives composition, - Class inheritance of host bindings, - Dynamic component instantiation (createComponent with custom hostElement or dynamic directives), - Elements with SVG/MathML namespaces (e.g. <svg:a>, <math>), or - Elements using tag-neutral selectors (e.g. :not(...)),
the compiler either failed to associate a sanitizer with the host binding or attached an incorrect security context. As a result, untrusted inputs (e.g. javascript:... URLs) bound via the host binding would be written to the DOM attribute without passing through Angular's built-in sanitizer.
Impact An attacker capable of controlling the value bound to an affected directive host binding could execute arbitrary JavaScript in the user's browser context (Cross-Site Scripting).
Patches This issue has been resolved in versions: - 22.1.0 - 21.2.20 - 20.3.28
Workarounds Ensure that any user-controlled values assigned to properties bound via directive host bindings are explicitly sanitized using DomSanitizer.sanitize(SecurityContext.URL, ...) before assignment, or restrict the input to validated safe URL schemes (e.g. http://, https://).
Angular is a development platform for building mobile and desktop web applications using TypeScript/JavaScript and other languages. Prior to 20.3.30, 21.2.22, and 22.1.4, Angular server-side rendering (SSR) in @angular/platform-server serializes ProcessingInstruction DOM nodes inside fallback raw-content elements without escaping matching ancestor closing tags. ProcessingInstruction data escaped greater-than characters but left less-than characters untouched and did not inspect fallback ancestors, so data such as a matching closing tag prematurely terminates noscript, iframe, noembed, or noframes containers. The vulnerable nodes cannot be authored through standard Angular templates; reachability requires application or library code using inject(DOCUMENT).createProcessingInstruction with attacker-controlled data or Renderer2 DOM insertion inside a fallback container. In HTML5 RAWTEXT parsing, the premature close causes subsequent sibling elements to be interpreted as live HTML and enables arbitrary JavaScript execution in a victim's browser. This issue is fixed in versions 20.3.30, 21.2.22, and 22.1.4.
Summary An XSS vulnerability exists in @angular/platform-server during server-side rendering (SSR) HTML serialization when traversing ancestor tags across <template> element boundaries. When an application renders untrusted user input within raw-text tags (<xmp>, <style>, <script>), comments, or text nodes inside a <template> that is nested within a fallback raw-content element (<noscript>, <iframe>, <noembed>, <noframes>), matching closing tags (e.g., </noscript>) are not escaped during HTML serialization. When rendered in a browser, this unescaped closing tag prematurely terminates the fallback container and executes trailing markup as active DOM elements.
Technical Description In HTML5 parsing, fallback raw-content elements (<noscript>, <iframe>, <noembed>, <noframes>) place the browser's tokenizer into RAWTEXT mode. In this mode, inner content is parsed as literal text until an end tag matching the container tag name (e.g., </noscript>) is encountered.
To prevent XSS breakout vectors during SSR serialization, the DOM serializer inspects a node's ancestors to escape any matching fallback closing tags (</tag -> </tag). However: 1. Per DOM specifications, the children of a <template> element reside in a separate DocumentFragment (template.content), whose own parentNode is null. 2. The serializer's ancestor traversal previously only inspected element nodes. When traversing upward from a node inside template.content, traversal terminated immediately at the DocumentFragment boundary. 3. Because traversal stopped before reaching the outer document tree, enclosing fallback raw-content ancestors (such as <noscript> or <iframe>) were not discovered. As a result, closing sequences like </noscript> within <template> content were emitted unescaped.
Impact & Reachability Framework Guarantee Bypass: Angular guarantees that standard text interpolation ({{ userInput }} bound as element text content) is safe by default without manual sanitization. This vulnerability bypasses that guarantee during SSR HTML serialization when untrusted input is interpolated inside template content within fallback containers. Template Authoring: Writing literal <xmp> or <style> directly inside a component's <template> markup requires relaxed template schema checks (CUSTOMELEMENTSSCHEMA or NOERRORSSCHEMA). However, standard HTML comments and text nodes inside <template> within <noscript> are reachable without relaxed schemas. Imperative DOM Construction: Components or directives that construct DOM structures imperatively via Renderer2 bypass template compiler schema checks entirely and are unconditionally affected.
Proof of Concept (Minimal Reproduction) ts import { Component } from '@angular/core';
@Component({ selector: 'app-root', standalone: true, template: <noscript> <template> <xmp>{{ payload }}</xmp> </template> </noscript> }) export class AppComponent { // Attacker-controlled input bound via standard text interpolation payload = '</noscript><img src=x onerror=alert("SSRTEMPLATEXSS")>'; } Vulnerable SSR Output: html <noscript><template><xmp></noscript><img src=x onerror=alert("SSRTEMPLATEXSS")></xmp></template></noscript>
Workarounds Avoid rendering untrusted user input inside <template> elements nested within <noscript>, <iframe>, <noembed>, or <noframes> in server-rendered templates. Avoid programmatic DOM assembly of <template> elements inside fallback containers when handling untrusted data.
End of life: 6/30/2028, End of support: 6/30/2027, Latest version: 22.2.0
A reflected cross-site scripting (XSS) vulnerability in CKeditor v46.1.0 & Angular v18.0.0 allows attackers to execute arbitrary code in the context of a user's browser via injecting a crafted payload.
End of life: 6/30/2027, End of support: 6/3/2026, Latest version: 21.2.24
End of life: 11/28/2026, End of support: 11/19/2025, Latest version: 20.3.32
End of life: 5/19/2026, End of support: 5/28/2025, Latest version: 19.2.25
End of life: 5/19/2026, End of support: 5/28/2025, Latest version: 19.2.25
End of life: 11/21/2025, End of support: 11/19/2024, Latest version: 18.2.14
End of life: 11/21/2025, End of support: 11/19/2024, Latest version: 18.2.14
End of life: 5/15/2025, End of support: 5/8/2024, Latest version: 17.3.12
End of life: 5/15/2025, End of support: 5/8/2024, Latest version: 17.3.12
End of life: 11/8/2024, End of support: 11/8/2023, Latest version: 16.2.12
End of life: 11/8/2024, End of support: 11/8/2023, Latest version: 16.2.12
End of life: 5/18/2024, End of support: 5/3/2023, Latest version: 15.2.10
End of life: 5/18/2024, End of support: 5/3/2023, Latest version: 15.2.10
End of life: 11/18/2023, End of support: 11/18/2022, Latest version: 14.3.0
End of life: 11/18/2023, End of support: 11/18/2022, Latest version: 14.3.0
End of life: 5/4/2023, End of support: 6/2/2022, Latest version: 13.4.0
End of life: 5/4/2023, End of support: 6/2/2022, Latest version: 13.4.0
End of life: 11/12/2022, End of support: 11/12/2021, Latest version: 12.2.17
End of life: 11/12/2022, End of support: 11/12/2021, Latest version: 12.2.17
End of life: 5/11/2022, End of support: 5/11/2021, Latest version: 11.2.14
End of life: 5/11/2022, End of support: 5/11/2021, Latest version: 11.2.14
End of life: 12/24/2021, End of support: 12/24/2020, Latest version: 10.2.5
End of life: 12/24/2021, End of support: 12/24/2020, Latest version: 10.2.5
End of life: 8/6/2021, End of support: 8/6/2020, Latest version: 9.1.13
End of life: 8/6/2021, End of support: 8/6/2020, Latest version: 9.1.13