diff --git a/packages/core/src/i18n/locale_data_api.ts b/packages/core/src/i18n/locale_data_api.ts index 21bd136501a6..910a8e169ae5 100644 --- a/packages/core/src/i18n/locale_data_api.ts +++ b/packages/core/src/i18n/locale_data_api.ts @@ -102,11 +102,20 @@ export function getLocalePluralCase(locale: string): (value: number) => number { */ export function getLocaleData(normalizedLocale: string): any { if (!(normalizedLocale in LOCALE_DATA)) { - LOCALE_DATA[normalizedLocale] = + const globalLocaleData = global.ng && global.ng.common && global.ng.common.locales && global.ng.common.locales[normalizedLocale]; + // Only cache global locale data when an entry is actually found, to avoid + // caching missing lookups. In SSR this cache is process-wide across requests, + // so caching `undefined` would retain attacker-controlled locale identifiers + // indefinitely. It would also make the `in` check above short-circuit on + // subsequent lookups and skip the global fallback. + if (globalLocaleData !== undefined) { + LOCALE_DATA[normalizedLocale] = globalLocaleData; + } + return globalLocaleData; } return LOCALE_DATA[normalizedLocale]; } diff --git a/packages/core/test/i18n/locale_data_api_spec.ts b/packages/core/test/i18n/locale_data_api_spec.ts index e6367f8fa0cd..81be3955007d 100644 --- a/packages/core/test/i18n/locale_data_api_spec.ts +++ b/packages/core/test/i18n/locale_data_api_spec.ts @@ -85,6 +85,16 @@ describe('locale data api', () => { expect(findLocaleData('de-CH')).toEqual(localeDeCH); }); + it('should not cache missing global locale data lookups', () => { + const localeEnNZ: any[] = ['en-NZ']; + + expect(findLocaleData('en-NZ')).toEqual(localeEn); + + global.ng.common.locales['en-nz'] = localeEnNZ; + + expect(findLocaleData('en-NZ')).toBe(localeEnNZ); + }); + it('should find the parent LOCALE_DATA if the exact locale is not available and the parent locale is on the global object', () => { expect(findLocaleData('de-BE')).toEqual(localeDe); });