CVE-2025-66035
Description
Angular is a development platform for building mobile and desktop web applications using TypeScript/JavaScript and other languages. Prior to versions 19.2.16, 20.3.14, and 21.0.1, there is a XSRF token leakage via protocol-relative URLs in angular HTTP clients. The vulnerability is a Credential Leak by App Logic that leads to the unauthorized disclosure of the Cross-Site Request Forgery (XSRF) token to an attacker-controlled domain. Angular's HttpClient has a built-in XSRF protection mechanism that works by checking if a request URL starts with a protocol (http:// or https://) to determine if it is cross-origin. If the URL starts with protocol-relative URL (//), it is incorrectly treated as a same-origin request, and the XSRF token is automatically added to the X-XSRF-TOKEN header. This issue has been patched in versions 19.2.16, 20.3.14, and 21.0.1. A workaround for this issue involves avoiding using protocol-relative URLs (URLs starting with //) in HttpClient requests. All backend communication URLs should be hardcoded as relative paths (starting with a single /) or fully qualified, trusted absolute URLs.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
@angular/commonnpm | >= 21.0.0-next.0, < 21.0.1 | 21.0.1 |
@angular/commonnpm | >= 20.0.0-next.0, < 20.3.14 | 20.3.14 |
@angular/commonnpm | < 19.2.16 | 19.2.16 |
Affected products
1Patches
30276479e7d0efix(http): prevent XSRF token leakage to protocol-relative URLs
2 files changed · +23 −3
packages/common/http/src/xsrf.ts+6 −3 modified@@ -82,11 +82,15 @@ export class HttpXsrfCookieExtractor implements HttpXsrfTokenExtractor { } } +/** + * Regex to match absolute URLs, including protocol-relative URLs. + */ +const ABSOLUTE_URL_REGEX = /^(?:https?:)?\/\//i; + export function xsrfInterceptorFn( req: HttpRequest<unknown>, next: HttpHandlerFn, ): Observable<HttpEvent<unknown>> { - const lcUrl = req.url.toLowerCase(); // Skip both non-mutating requests and absolute URLs. // Non-mutating requests don't require a token, and absolute URLs require special handling // anyway as the cookie set @@ -95,8 +99,7 @@ export function xsrfInterceptorFn( !inject(XSRF_ENABLED) || req.method === 'GET' || req.method === 'HEAD' || - lcUrl.startsWith('http://') || - lcUrl.startsWith('https://') + ABSOLUTE_URL_REGEX.test(req.url) ) { return next(req); }
packages/common/http/test/xsrf_spec.ts+17 −0 modified@@ -70,6 +70,23 @@ describe('HttpXsrfInterceptor', () => { expect(req.request.headers.has('X-XSRF-TOKEN')).toEqual(false); req.flush({}); }); + + it('does not apply XSRF protection when request is absolute', () => { + interceptor + .intercept(new HttpRequest('POST', 'https://example.com/test', {}), backend) + .subscribe(); + const req = backend.expectOne('https://example.com/test'); + expect(req.request.headers.has('X-XSRF-TOKEN')).toBeFalse(); + req.flush({}); + }); + + it('does not apply XSRF protection when request is protocol relative', () => { + interceptor.intercept(new HttpRequest('POST', '//example.com/test', {}), backend).subscribe(); + const req = backend.expectOne('//example.com/test'); + expect(req.request.headers.has('X-XSRF-TOKEN')).toBeFalse(); + req.flush({}); + }); + it('does not overwrite existing header', () => { interceptor .intercept(
05fe6686a97ffix(http): prevent XSRF token leakage to protocol-relative URLs
2 files changed · +23 −3
packages/common/http/src/xsrf.ts+6 −3 modified@@ -82,11 +82,15 @@ export class HttpXsrfCookieExtractor implements HttpXsrfTokenExtractor { } } +/** + * Regex to match absolute URLs, including protocol-relative URLs. + */ +const ABSOLUTE_URL_REGEX = /^(?:https?:)?\/\//i; + export function xsrfInterceptorFn( req: HttpRequest<unknown>, next: HttpHandlerFn, ): Observable<HttpEvent<unknown>> { - const lcUrl = req.url.toLowerCase(); // Skip both non-mutating requests and absolute URLs. // Non-mutating requests don't require a token, and absolute URLs require special handling // anyway as the cookie set @@ -95,8 +99,7 @@ export function xsrfInterceptorFn( !inject(XSRF_ENABLED) || req.method === 'GET' || req.method === 'HEAD' || - lcUrl.startsWith('http://') || - lcUrl.startsWith('https://') + ABSOLUTE_URL_REGEX.test(req.url) ) { return next(req); }
packages/common/http/test/xsrf_spec.ts+17 −0 modified@@ -70,6 +70,23 @@ describe('HttpXsrfInterceptor', () => { expect(req.request.headers.has('X-XSRF-TOKEN')).toEqual(false); req.flush({}); }); + + it('does not apply XSRF protection when request is absolute', () => { + interceptor + .intercept(new HttpRequest('POST', 'https://example.com/test', {}), backend) + .subscribe(); + const req = backend.expectOne('https://example.com/test'); + expect(req.request.headers.has('X-XSRF-TOKEN')).toBeFalse(); + req.flush({}); + }); + + it('does not apply XSRF protection when request is protocol relative', () => { + interceptor.intercept(new HttpRequest('POST', '//example.com/test', {}), backend).subscribe(); + const req = backend.expectOne('//example.com/test'); + expect(req.request.headers.has('X-XSRF-TOKEN')).toBeFalse(); + req.flush({}); + }); + it('does not overwrite existing header', () => { interceptor .intercept(
3240d856d942fix(http): prevent XSRF token leakage to protocol-relative URLs
2 files changed · +23 −3
packages/common/http/src/xsrf.ts+6 −3 modified@@ -91,11 +91,15 @@ export abstract class HttpXsrfTokenExtractor { abstract getToken(): string | null; } +/** + * Regex to match absolute URLs, including protocol-relative URLs. + */ +const ABSOLUTE_URL_REGEX = /^(?:https?:)?\/\//i; + export function xsrfInterceptorFn( req: HttpRequest<unknown>, next: HttpHandlerFn, ): Observable<HttpEvent<unknown>> { - const lcUrl = req.url.toLowerCase(); // Skip both non-mutating requests and absolute URLs. // Non-mutating requests don't require a token, and absolute URLs require special handling // anyway as the cookie set @@ -104,8 +108,7 @@ export function xsrfInterceptorFn( !inject(XSRF_ENABLED) || req.method === 'GET' || req.method === 'HEAD' || - lcUrl.startsWith('http://') || - lcUrl.startsWith('https://') + ABSOLUTE_URL_REGEX.test(req.url) ) { return next(req); }
packages/common/http/test/xsrf_spec.ts+17 −0 modified@@ -71,6 +71,23 @@ describe('HttpXsrfInterceptor', () => { expect(req.request.headers.has('X-XSRF-TOKEN')).toEqual(false); req.flush({}); }); + + it('does not apply XSRF protection when request is absolute', () => { + interceptor + .intercept(new HttpRequest('POST', 'https://example.com/test', {}), backend) + .subscribe(); + const req = backend.expectOne('https://example.com/test'); + expect(req.request.headers.has('X-XSRF-TOKEN')).toBeFalse(); + req.flush({}); + }); + + it('does not apply XSRF protection when request is protocol relative', () => { + interceptor.intercept(new HttpRequest('POST', '//example.com/test', {}), backend).subscribe(); + const req = backend.expectOne('//example.com/test'); + expect(req.request.headers.has('X-XSRF-TOKEN')).toBeFalse(); + req.flush({}); + }); + it('does not overwrite existing header', () => { interceptor .intercept(
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
9- github.com/advisories/GHSA-58c5-g7wp-6w37ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-66035ghsaADVISORY
- github.com/angular/angular/commit/0276479e7d0e280e0f8d26fa567d3b7aa97a516fnvdWEB
- github.com/angular/angular/commit/05fe6686a97fa0bcd3cf157805b3612033f975bcnvdWEB
- github.com/angular/angular/commit/3240d856d942727372a705252f7c8c115394a41envdWEB
- github.com/angular/angular/releases/tag/19.2.16nvdWEB
- github.com/angular/angular/releases/tag/20.3.14nvdWEB
- github.com/angular/angular/releases/tag/21.0.1nvdWEB
- github.com/angular/angular/security/advisories/GHSA-58c5-g7wp-6w37nvdWEB
News mentions
0No linked articles in our index yet.