CVE-2023-34109 (TypeScript, published 2023-06-07T18:15Z)
Description: value: zxcvbn-ts is an open source password strength estimator written in typescript. This vulnerability affects users running on the nodeJS platform which are using the second argument of the zxcvbn function. It can result in an unbounded resource consumption as the user inputs array is extended with every function call. Browsers are impacted, too but a single user need to do a lot of input changes so that it affects the browser, while the node process gets the inputs of every user of a platform and can be killed that way. This problem has been patched in version 3.0.2. Users are advised to upgrade. Users unable to upgrade should stop using the second argument of the zxcvbn function and use the zxcvbnOptions.setOptions function.
Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-400 \u2014 Uncontrolled Resource Consumption
The product does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.
Repository: https://github.com/zxcvbn-ts/zxcvbn
Files changed:
packages/libraries/main/src/Options.ts
Commit message: fix(user-inputs): resolve memory leak with user inputs on the fly


Vulnerable code:
      rankedDictionaries[name] = this.getRankedDictionary(name)
        this.getRankedDictionariesMaxWordSize(name)
  getRankedDictionariesMaxWordSize(name: string) {
    const data = this.dictionary[name].map((el) => {
  getRankedDictionary(name: string) {
    const list = this.dictionary[name]
    if (name === 'userInputs') {
      const sanitizedInputs: string[] = []

      list.forEach((input: string | number | boolean) => {
        const inputType = typeof input
        if (
          inputType === 'string' ||
          inputType === 'number' ||
          inputType === 'boolean'
        ) {
          sanitizedInputs.push(input.toString().toLowerCase())
        }
      })

      return buildRankedDictionary(sanitizedInputs)
    }
    return buildRankedDictionary(list)
    if (this.dictionary.userInputs) {
      this.dictionary.userInputs = [
        ...this.dictionary.userInputs,
        ...dictionary,
      ]
    } else {
      this.dictionary.userInputs = dictionary
    this.rankedDictionaries.userInputs = this.getRankedDictionary('userInputs')
      this.getRankedDictionariesMaxWordSize('userInputs')

Fixed code:
      rankedDictionaries[name] = buildRankedDictionary(this.dictionary[name])
        this.getRankedDictionariesMaxWordSize(this.dictionary[name])
  getRankedDictionariesMaxWordSize(list: (string | number)[]) {
    const data = list.map((el) => {
  buildSanitizedRankedDictionary(list: (string | number)[]) {
    const sanitizedInputs: string[] = []

    list.forEach((input: string | number | boolean) => {
      const inputType = typeof input
      if (
        inputType === 'string' ||
        inputType === 'number' ||
        inputType === 'boolean'
      ) {
        sanitizedInputs.push(input.toString().toLowerCase())
      }
    })

    return buildRankedDictionary(sanitizedInputs)
    if (!this.dictionary.userInputs) {
      this.dictionary.userInputs = []
    const newList = [...this.dictionary.userInputs, ...dictionary]
    this.rankedDictionaries.userInputs =
      this.buildSanitizedRankedDictionary(newList)
      this.getRankedDictionariesMaxWordSize(newList)