From 3b17e3c1e57a27a2d8e47d6ca48b5bc6fde18939 Mon Sep 17 00:00:00 2001 From: Muhammad Abdul Rehman Date: Sun, 22 Mar 2026 02:24:33 +0500 Subject: [PATCH] fix(@angular/ssr): validate decoded x-forwarded-prefix before prefix checks --- packages/angular/ssr/src/utils/validation.ts | 17 ++++++++--- .../angular/ssr/test/utils/validation_spec.ts | 30 +++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/packages/angular/ssr/src/utils/validation.ts b/packages/angular/ssr/src/utils/validation.ts index cb1bf6ecb56b..9e689b770913 100644 --- a/packages/angular/ssr/src/utils/validation.ts +++ b/packages/angular/ssr/src/utils/validation.ts @@ -268,9 +268,18 @@ function validateHeaders(request: Request): void { } const xForwardedPrefix = getFirstHeaderValue(headers.get('x-forwarded-prefix')); - if (xForwardedPrefix && INVALID_PREFIX_REGEX.test(xForwardedPrefix)) { - throw new Error( - 'Header "x-forwarded-prefix" must not start with "\\" or multiple "/" or contain ".", ".." path segments.', - ); + if (xForwardedPrefix) { + let decodedXForwardedPrefix: string; + try { + decodedXForwardedPrefix = decodeURIComponent(xForwardedPrefix); + } catch { + throw new Error('Header "x-forwarded-prefix" contains an invalid percent-encoded sequence.'); + } + + if (INVALID_PREFIX_REGEX.test(decodedXForwardedPrefix)) { + throw new Error( + 'Header "x-forwarded-prefix" must not start with "\\" or multiple "/" or contain ".", ".." path segments.', + ); + } } } diff --git a/packages/angular/ssr/test/utils/validation_spec.ts b/packages/angular/ssr/test/utils/validation_spec.ts index d8c3eaeebdb3..62c8bc9b1625 100644 --- a/packages/angular/ssr/test/utils/validation_spec.ts +++ b/packages/angular/ssr/test/utils/validation_spec.ts @@ -213,6 +213,36 @@ describe('Validation Utils', () => { .not.toThrow(); } }); + + it('should throw error if x-forwarded-prefix contains percent-encoded invalid prefixes', () => { + const inputs = ['%5Cevil.com', '%2F%2Fevil.com', '%2F..%2Fevil.com']; + + for (const prefix of inputs) { + const request = new Request('https://example.com', { + headers: { + 'x-forwarded-prefix': prefix, + }, + }); + + expect(() => validateRequest(request, allowedHosts, false)) + .withContext(`Prefix: "${prefix}"`) + .toThrowError( + 'Header "x-forwarded-prefix" must not start with "\\" or multiple "/" or contain ".", ".." path segments.', + ); + } + }); + + it('should throw error if x-forwarded-prefix contains malformed percent-encoding', () => { + const request = new Request('https://example.com', { + headers: { + 'x-forwarded-prefix': '/%ZZ', + }, + }); + + expect(() => validateRequest(request, allowedHosts, false)).toThrowError( + 'Header "x-forwarded-prefix" contains an invalid percent-encoded sequence.', + ); + }); }); describe('cloneRequestAndPatchHeaders', () => {