@angular/common: Denial of Service (DoS) via OOM in Number Formatting (digitsInfo)
Description
Angular's formatNumber function lacks upper bounds validation on the digitsInfo parameter, leading to an unbounded loop that causes Denial of Service.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Angular's formatNumber function lacks upper bounds validation on the digitsInfo parameter, leading to an unbounded loop that causes Denial of Service.
Vulnerability
The formatNumber function in @angular/common (used by DecimalPipe, PercentPipe, and CurrencyPipe) does not validate the upper bounds of the digitsInfo parameter [1][2]. When parsing a string such as 1.200000000-200000000, the minimum and maximum fraction digits are converted to integers without limits, causing the internal roundNumber function to enter an unbounded loop that repeatedly pushes elements into an array [2][3]. This affects Angular versions prior to 22.0.0-rc.2, 21.2.15, 20.3.22, and 19.2.23 [2][3].
Exploitation
An attacker must provide a maliciously crafted digitsInfo value to an application that uses Angular's number formatting utilities and where that parameter is controllable via untrusted input (e.g., query parameters, user preference settings, or API responses) [2][3]. The attack does not require authentication; a simple HTTP request or any vector that passes the crafted string to the vulnerable function suffices. The roundNumber function then attempts to pad the digits array to the requested fraction size, causing resource exhaustion [2][3].
Impact
Successful exploitation leads to Denial of Service (DoS) via resource exhaustion [1][2][3]. On the server side (SSR with @angular/ssr), the Node.js process crashes with a JavaScript heap out of memory error, affecting all application users [2][3]. On the client side, the browser tab freezes and becomes unresponsive due to the main thread being blocked [2][3].
Mitigation
Patches are available in Angular versions 22.0.0-rc.2, 21.2.15, 20.3.22, and 19.2.23 [2][3]. Upgrading to a fixed version is the recommended remediation. No workarounds are mentioned; if the application cannot be upgraded immediately, ensure the digitsInfo parameter is restricted to a known, safe range and is not sourced from untrusted input [2][3].
AI Insight generated on Jun 15, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1Patches
1dfdfbe34a57dfix(common): add upper bounds for digitsInfo
2 files changed · +28 −2
packages/common/src/i18n/format_number.ts+15 −1 modified@@ -8,14 +8,14 @@ import {ɵRuntimeError as RuntimeError} from '@angular/core'; +import {RuntimeErrorCode} from '../errors'; import { getLocaleNumberFormat, getLocaleNumberSymbol, getNumberOfCurrencyDigits, NumberFormatStyle, NumberSymbol, } from './locale_data_api'; -import {RuntimeErrorCode} from '../errors'; export const NUMBER_FORMAT_REGEXP = /^(\d+)?\.((\d+)(-(\d+))?)?$/; const MAX_DIGITS = 22; @@ -77,6 +77,20 @@ function formatNumberToLocaleString( } else if (minFractionPart != null && minFraction > maxFraction) { maxFraction = minFraction; } + + // Prevent DoS via resource exhaustion by capping the maximum padding iterations + const MAX_ALLOWED_DIGITS = 100; + if ( + minInt > MAX_ALLOWED_DIGITS || + minFraction > MAX_ALLOWED_DIGITS || + maxFraction > MAX_ALLOWED_DIGITS + ) { + throw new RuntimeError( + RuntimeErrorCode.INVALID_DIGIT_INFO, + ngDevMode && + `${digitsInfo} is not a valid digit info. Exceeded maximum limits of ${MAX_ALLOWED_DIGITS} digits.`, + ); + } } roundNumber(parsedNumber, minFraction, maxFraction);
packages/common/test/i18n/format_number_spec.ts+13 −1 modified@@ -6,12 +6,12 @@ * found in the LICENSE file at https://angular.dev/license */ +import {ɵDEFAULT_LOCALE_ID, ɵregisterLocaleData, ɵunregisterLocaleData} from '@angular/core'; import {formatCurrency, formatNumber, formatPercent} from '../../index'; import localeAr from '../../locales/ar'; import localeEn from '../../locales/en'; import localeEsUS from '../../locales/es-US'; import localeFr from '../../locales/fr'; -import {ɵDEFAULT_LOCALE_ID, ɵregisterLocaleData, ɵunregisterLocaleData} from '@angular/core'; describe('Format number', () => { beforeAll(() => { @@ -43,6 +43,18 @@ describe('Format number', () => { /is higher than the maximum/, ); }); + + it('should throw if minInt, minFraction, or maxFraction exceeds 100 to prevent DoS', () => { + const expectedError = /Exceeded maximum limits of 100 digits/; + expect(() => formatNumber(1.1, ɵDEFAULT_LOCALE_ID, '101.4-5')).toThrowError(expectedError); + expect(() => formatNumber(1.1, ɵDEFAULT_LOCALE_ID, '3.101-105')).toThrowError( + expectedError, + ); + expect(() => formatNumber(1.1, ɵDEFAULT_LOCALE_ID, '3.4-101')).toThrowError(expectedError); + expect(() => formatNumber(1.1, ɵDEFAULT_LOCALE_ID, '1.2000000000-20000000')).toThrowError( + expectedError, + ); + }); }); describe('transform with custom locales', () => {
Vulnerability mechanics
Synthesis attempt was rejected by the grounding validator. Re-run pending.
References
3News mentions
0No linked articles in our index yet.