diff --git a/spec/PagesRouter.spec.js b/spec/PagesRouter.spec.js index 4864ceb42d..0d0afbc39e 100644 --- a/spec/PagesRouter.spec.js +++ b/spec/PagesRouter.spec.js @@ -1396,15 +1396,31 @@ describe('Pages Router', () => { expect(response.text).toContain('<img'); }); - it('should escape XSS in locale parameter', async () => { + it('should reject XSS payload in locale parameter', async () => { const xssLocale = '">'; const response = await request({ url: `http://localhost:8378/1/apps/choose_password?locale=${encodeURIComponent(xssLocale)}&appId=test`, }); expect(response.status).toBe(200); + // Invalid locale is rejected by format validation, so the XSS + // payload never reaches the page content expect(response.text).not.toContain(''); - expect(response.text).toContain('"><svg'); + expect(response.text).not.toContain('"><svg'); + }); + + it('should reject non-ASCII characters in locale parameter', async () => { + // Non-ASCII characters like ğ (U+011F) would cause ERR_INVALID_CHAR + // when set as HTTP header value if not rejected by locale validation + const nonAsciiLocale = 'ğ'; + const response = await request({ + url: `http://localhost:8378/1/apps/choose_password?locale=${encodeURIComponent(nonAsciiLocale)}&appId=test`, + }); + + expect(response.status).toBe(200); + // Non-ASCII locale is rejected by format validation; + // no ERR_INVALID_CHAR error occurs + expect(response.headers['x-parse-page-param-locale']).toBeUndefined(); }); it('should handle legitimate usernames with quotes correctly', async () => { diff --git a/src/Routers/PagesRouter.js b/src/Routers/PagesRouter.js index 7b6ddf8b6c..49c3aab138 100644 --- a/src/Routers/PagesRouter.js +++ b/src/Routers/PagesRouter.js @@ -555,6 +555,16 @@ export class PagesRouter extends PromiseRouter { (req.body || {})[pageParams.locale] || (req.params || {})[pageParams.locale] || (req.headers || {})[pageParamHeaderPrefix + pageParams.locale]; + + // Validate locale format to prevent path traversal and invalid + // HTTP header characters; only allow standard locale patterns + // like "en", "en-US", "de-AT", "zh-Hans-CN" + if (locale !== undefined && typeof locale !== 'string') { + return undefined; + } + if (typeof locale === 'string' && !/^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$/.test(locale)) { + return undefined; + } return locale; }