VYPR
High severity8.2GHSA Advisory· Published Jun 15, 2026· Updated Jun 15, 2026

@angular/common: Denial of Service (DoS) via OOM in Number Formatting (digitsInfo)

CVE-2026-50171

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

1

Patches

1
dfdfbe34a57d

fix(common): add upper bounds for digitsInfo

https://github.com/angular/angularMatthieu RieglerMay 20, 2026via body-scan-shorthand
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

3

News mentions

0

No linked articles in our index yet.