chore(deps): update dependency brace-expansion to v5.0.9 [security] #22

Closed
renovate-bot wants to merge 1 commit from renovate/npm-brace-expansion-vulnerability into main
Member

This PR contains the following updates:

Package Change Age Confidence
brace-expansion 5.0.8 → 5.0.9 age confidence

brace-expansion: DoS via unbounded intermediate arrays, bypassing the CVE-2026-14257 mitigation

CVE-2026-69152 / GHSA-rgw5-rvv9-x895

More information

Details

Summary

The maxLength mitigation added in 5.0.8 for GHSA-mh99-v99m-4gvg / CVE-2026-14257 is incomplete. It bounds the accumulator where results are combined, but not the intermediate arrays that feed it. A ~25 KB input still crashes the Node process with an uncatchable out-of-memory error, so try/catch around expand() does not help.

A second, related path in the same function lets a ~400 KB input block the event loop for over two minutes without ever exceeding the memory bound.

Details

maxLength was enforced in combine(), the single place output grows. Two arrays are built before combine() runs, and neither was bounded.

1. Comma alternatives accumulate without a running total (memory exhaustion)

Each alternative in {a,b,c,...} is expanded by its own recursive expand_() call, so each receives a full, independent maxLength allowance. The results were then concatenated into a single values array with no cumulative limit:

values = []
for (let j = 0; j < n.length; j++) {
  values.push.apply(values, expand_(n[j], max, maxLength, false))
}

acc = combine(acc, pre, values, max, maxLength, ...)

With A alternatives, values can reach A * maxLength characters before combine() gets a chance to truncate it. At the default maxLength of 4,000,000 and 400 alternatives, that is well past any default heap.

2. Padded sequences ignore maxLength while generating (CPU exhaustion)

expandSequence() was bounded by max (the result count) but never consulted maxLength. A padded sequence's element width follows the input, so {0...01..100000} with a wide pad generates max elements, each as wide as the input, only for combine() to discard all but a handful.

Memory stays flat here, because V8 represents the padded strings as cons-strings, which is likely why this path was not caught alongside the original issue. The cost is time: work proportional to max * width.

pad width input bytes results kept time (5.0.8) time (patched)
20,000 20 KB 199 ~7.3 s ~20 ms
100,000 100 KB 39 ~32 s ~20 ms
400,000 400 KB 9 ~124 s ~18 ms

Output is byte-identical before and after the fix; only the wasted work is removed.

Proof of concept

Memory exhaustion, against 5.0.8:

import { expand } from 'brace-expansion'

const part = '{' + '0'.repeat(50) + '1..100000}'
const input = '{' + Array(400).fill(part).join(',') + '}'  // ~25 KB

try {
  expand(input)
} catch (e) {
  // never reached - the process is already dead
}
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
Aborted

Event-loop stall, against 5.0.8:

import { expand } from 'brace-expansion'

// ~400 KB input, returns 9 results after roughly two minutes of blocking CPU
expand('{' + '0'.repeat(400_000) + '1..100000}')
Impact

Denial of service. Any application that passes attacker-controlled input to expand(), directly or transitively through a glob or pattern-matching library, can be remotely crashed or stalled. The out-of-memory variant terminates the process and cannot be handled with try/catch.

Applications already on 5.0.8 are affected: the 5.0.8 mitigation does not cover these paths.

Patches

Both intermediate arrays are now bounded as they are built, using the same max and maxLength limits already applied in combine():

  • values tracks a running result count and character length while alternatives are appended, and stops once either bound is reached.
  • expandSequence() accepts maxLength and stops generating once the sequence's own characters reach it.

As with the existing limits, output is truncated rather than allowed to grow without bound, which matches how max already behaves. The defaults sit well above any realistic expansion, so legitimate input is unaffected.

Workarounds

If upgrading is not immediately possible, avoid passing untrusted input to expand() or to glob brace patterns, or pass an explicitly small max and maxLength.

Note that a small maxLength alone was not sufficient on affected versions: it was applied per alternative rather than cumulatively, which is the root of the first issue above.

Credits

The memory-exhaustion bypass was reported by Alessio Della Libera, CEO & Co-founder at Numyra.

The sequence-generation issue was found while verifying that report.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).

❗ Important

Release Notes retrieval for this PR were skipped because no github.com credentials were available.
If you are self-hosted, please see this instruction.


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR has been generated by Mend Renovate.

This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [brace-expansion](https://github.com/juliangruber/brace-expansion) | [`5.0.8` → `5.0.9`](https://renovatebot.com/diffs/npm/brace-expansion/5.0.8/5.0.9) | ![age](https://developer.mend.io/api/mc/badges/age/npm/brace-expansion/5.0.9?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/brace-expansion/5.0.8/5.0.9?slim=true) | --- ### brace-expansion: DoS via unbounded intermediate arrays, bypassing the CVE-2026-14257 mitigation [CVE-2026-69152](https://nvd.nist.gov/vuln/detail/CVE-2026-69152) / [GHSA-rgw5-rvv9-x895](https://github.com/advisories/GHSA-rgw5-rvv9-x895) <details> <summary>More information</summary> #### Details ##### Summary The `maxLength` mitigation added in `5.0.8` for GHSA-mh99-v99m-4gvg / CVE-2026-14257 is incomplete. It bounds the accumulator where results are *combined*, but not the intermediate arrays that feed it. A ~25 KB input still crashes the Node process with an **uncatchable** out-of-memory error, so `try/catch` around `expand()` does not help. A second, related path in the same function lets a ~400 KB input block the event loop for over two minutes without ever exceeding the memory bound. ##### Details `maxLength` was enforced in `combine()`, the single place output grows. Two arrays are built *before* `combine()` runs, and neither was bounded. **1. Comma alternatives accumulate without a running total (memory exhaustion)** Each alternative in `{a,b,c,...}` is expanded by its own recursive `expand_()` call, so each receives a full, independent `maxLength` allowance. The results were then concatenated into a single `values` array with no cumulative limit: ```js values = [] for (let j = 0; j < n.length; j++) { values.push.apply(values, expand_(n[j], max, maxLength, false)) } acc = combine(acc, pre, values, max, maxLength, ...) ``` With `A` alternatives, `values` can reach `A * maxLength` characters before `combine()` gets a chance to truncate it. At the default `maxLength` of 4,000,000 and 400 alternatives, that is well past any default heap. **2. Padded sequences ignore `maxLength` while generating (CPU exhaustion)** `expandSequence()` was bounded by `max` (the result *count*) but never consulted `maxLength`. A padded sequence's element width follows the input, so `{0...01..100000}` with a wide pad generates `max` elements, each as wide as the input, only for `combine()` to discard all but a handful. Memory stays flat here, because V8 represents the padded strings as cons-strings, which is likely why this path was not caught alongside the original issue. The cost is time: work proportional to `max * width`. | pad width | input bytes | results kept | time (5.0.8) | time (patched) | |---|---|---|---|---| | 20,000 | 20 KB | 199 | ~7.3 s | ~20 ms | | 100,000 | 100 KB | 39 | ~32 s | ~20 ms | | 400,000 | 400 KB | 9 | ~124 s | ~18 ms | Output is byte-identical before and after the fix; only the wasted work is removed. ##### Proof of concept Memory exhaustion, against `5.0.8`: ```js import { expand } from 'brace-expansion' const part = '{' + '0'.repeat(50) + '1..100000}' const input = '{' + Array(400).fill(part).join(',') + '}' // ~25 KB try { expand(input) } catch (e) { // never reached - the process is already dead } ``` ``` FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory Aborted ``` Event-loop stall, against `5.0.8`: ```js import { expand } from 'brace-expansion' // ~400 KB input, returns 9 results after roughly two minutes of blocking CPU expand('{' + '0'.repeat(400_000) + '1..100000}') ``` ##### Impact Denial of service. Any application that passes attacker-controlled input to `expand()`, directly or transitively through a glob or pattern-matching library, can be remotely crashed or stalled. The out-of-memory variant terminates the process and cannot be handled with `try/catch`. Applications already on `5.0.8` are affected: the `5.0.8` mitigation does not cover these paths. ##### Patches Both intermediate arrays are now bounded as they are built, using the same `max` and `maxLength` limits already applied in `combine()`: - `values` tracks a running result count and character length while alternatives are appended, and stops once either bound is reached. - `expandSequence()` accepts `maxLength` and stops generating once the sequence's own characters reach it. As with the existing limits, output is truncated rather than allowed to grow without bound, which matches how `max` already behaves. The defaults sit well above any realistic expansion, so legitimate input is unaffected. ##### Workarounds If upgrading is not immediately possible, avoid passing untrusted input to `expand()` or to glob brace patterns, or pass an explicitly small `max` **and** `maxLength`. Note that a small `maxLength` alone was not sufficient on affected versions: it was applied per alternative rather than cumulatively, which is the root of the first issue above. ##### Credits The memory-exhaustion bypass was reported by Alessio Della Libera, CEO & Co-founder at [Numyra](https://numyra.ai/). The sequence-generation issue was found while verifying that report. #### Severity - CVSS Score: 7.5 / 10 (High) - Vector String: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H` #### References - [https://github.com/juliangruber/brace-expansion/security/advisories/GHSA-rgw5-rvv9-x895](https://github.com/juliangruber/brace-expansion/security/advisories/GHSA-rgw5-rvv9-x895) - [https://nvd.nist.gov/vuln/detail/CVE-2026-69152](https://nvd.nist.gov/vuln/detail/CVE-2026-69152) - [https://github.com/juliangruber/brace-expansion/commit/139d015104e71433ad52a41d19467c48ecbb2c7d](https://github.com/juliangruber/brace-expansion/commit/139d015104e71433ad52a41d19467c48ecbb2c7d) - [https://github.com/juliangruber/brace-expansion/commit/1e30c930238d7162802d88a94189182def178dac](https://github.com/juliangruber/brace-expansion/commit/1e30c930238d7162802d88a94189182def178dac) - [https://github.com/juliangruber/brace-expansion/commit/688a99eeaab02627c2b89ba8ba4821fecfa659cf](https://github.com/juliangruber/brace-expansion/commit/688a99eeaab02627c2b89ba8ba4821fecfa659cf) - [https://github.com/juliangruber/brace-expansion/commit/cb4b9e47cc2ec777c14b2b4492fb431a56f6a031](https://github.com/juliangruber/brace-expansion/commit/cb4b9e47cc2ec777c14b2b4492fb431a56f6a031) - [https://github.com/juliangruber/brace-expansion](https://github.com/juliangruber/brace-expansion) This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-rgw5-rvv9-x895) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)). </details> > :exclamation: **Important** > > Release Notes retrieval for this PR were skipped because no github.com credentials were available. > If you are self-hosted, please see [this instruction](https://github.com/renovatebot/renovate/blob/master/docs/usage/examples/self-hosting.md#githubcom-token-for-release-notes). --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNzguMiIsInVwZGF0ZWRJblZlciI6IjQzLjI3OC4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJzZWN1cml0eS92dWxuIl19-->
chore(deps): update dependency brace-expansion to v5.0.9 [security]
Some checks failed
renovate/artifacts Artifact file update failure
baseline-security / baseline (push) Failing after 1m38s
scan / trivy-fs (push) Failing after 1m52s
ai-review / review (pull_request) Successful in 3m6s
baseline-security / baseline (pull_request) Failing after 1m38s
scan / trivy-fs (pull_request) Failing after 1m13s
6602898c64
Author
Member

⚠️ Artifact update problem

Renovate failed to update an artifact related to this branch. You probably do not want to merge this PR as-is.

♻ Renovate will retry this branch, including artifacts, only when one of the following happens:

  • any of the package files in this branch needs updating, or
  • the branch becomes conflicted, or
  • you click the rebase/retry checkbox if found above, or
  • you rename this PR's title to start with "rebase!" to trigger it manually

The artifact failure details are included below:

File name: package-lock.json
undefined
### ⚠️ Artifact update problem Renovate failed to update an artifact related to this branch. You probably do not want to merge this PR as-is. ♻ Renovate will retry this branch, including artifacts, only when one of the following happens: - any of the package files in this branch needs updating, or - the branch becomes conflicted, or - you click the rebase/retry checkbox if found above, or - you rename this PR's title to start with "rebase!" to trigger it manually The artifact failure details are included below: ##### File name: package-lock.json ``` undefined ```
ai-review-bot left a comment

AI review · advisory

Verdict: 1 thing worth fixing (1 medium).

Findings that didn't map to a diff line:

package.json:108 · MEDIUM — Lockfile out of sync after dependency version bump
The override for "brace-expansion" is updated to 5.0.9, but the lockfile (package-lock.json) is not updated, risking inconsistent dependency resolution across environments.

Fix: Run npm install to regenerate the lockfile and commit the updated package-lock.json.

⚑ panel: GPT-OSS 120B · Gemma 4 31B · Devstral 2 123B · Laguna S 2.1 — 1 distinct, 1 confirmed, 0 refuted · web: not used · context: 1 files · 90 codebase · 14 standards chunks (best-grounded: GPT-OSS 120B; smaller windows saw less)

Panel debate — how this review was reached

Grounding — context: 1 files · 90 codebase · 14 standards chunks (best-grounded: GPT-OSS 120B; smaller windows saw less)

Round 1 — independent reviews

  • GPT-OSS 120B (1 finding, confidence 0.97): The change updates the brace-expansion version but fails to update the lockfile, which may cause dependency mismatches.
  • Gemma 4 31B (0 findings, confidence 1.0): The diff is a standard security dependency update for brace-expansion within the package overrides, which is correct and consistent with agency security standards.
  • Devstral 2 123B (0 findings, confidence 1.0): The diff is a straightforward security update for the brace-expansion dependency, which is a correct and necessary change.
  • Laguna S 2.1 (0 findings, confidence 0.97): No findings: the diff is a single-line, well-formed security dependency bump of brace-expansion from 5.0.8 to 5.0.9, consistent with the repo's deterministic HIGH/CRITICAL vulnerability gate (see [sta

Round 2 — cross-examination

  • GPT-OSS 120B#1 Lockfile out of sync after dependency version bump · confirmed: Laguna S 2.1 · refuted: Gemma 4 31B, Devstral 2 123B

Synthesis — Devstral 2 123B wrote the final review from 1 confirmed finding.

Transcript rv-20260806165959-096542 — full round outputs, web results, and model reasoning are viewable by anyone with access to this repository via the AI gateway.

Advisory — never a merge gate. Disagree with a finding? Reply on it, or use the finding board under this review. Transcript rv-20260806165959-096542.

### AI review · advisory **Verdict: 1 thing worth fixing** (1 medium). Findings that didn't map to a diff line: **`package.json:108`** · MEDIUM — Lockfile out of sync after dependency version bump The override for "brace-expansion" is updated to 5.0.9, but the lockfile (package-lock.json) is not updated, risking inconsistent dependency resolution across environments. > **Fix:** Run `npm install` to regenerate the lockfile and commit the updated package-lock.json. <sub>⚑ panel: GPT-OSS 120B · Gemma 4 31B · Devstral 2 123B · Laguna S 2.1 — 1 distinct, 1 confirmed, 0 refuted · web: not used · context: 1 files · 90 codebase · 14 standards chunks (best-grounded: GPT-OSS 120B; smaller windows saw less)</sub> <details> <summary>Panel debate — how this review was reached</summary> **Grounding** — context: 1 files · 90 codebase · 14 standards chunks (best-grounded: GPT-OSS 120B; smaller windows saw less) **Round 1 — independent reviews** - **GPT-OSS 120B** (1 finding, confidence 0.97): The change updates the brace-expansion version but fails to update the lockfile, which may cause dependency mismatches. - **Gemma 4 31B** (0 findings, confidence 1.0): The diff is a standard security dependency update for `brace-expansion` within the package overrides, which is correct and consistent with agency security standards. - **Devstral 2 123B** (0 findings, confidence 1.0): The diff is a straightforward security update for the brace-expansion dependency, which is a correct and necessary change. - **Laguna S 2.1** (0 findings, confidence 0.97): No findings: the diff is a single-line, well-formed security dependency bump of brace-expansion from 5.0.8 to 5.0.9, consistent with the repo's deterministic HIGH/CRITICAL vulnerability gate (see [sta **Round 2 — cross-examination** - `GPT-OSS 120B#1` Lockfile out of sync after dependency version bump · confirmed: Laguna S 2.1 · refuted: Gemma 4 31B, Devstral 2 123B **Synthesis** — Devstral 2 123B wrote the final review from 1 confirmed finding. <sub>Transcript `rv-20260806165959-096542` — full round outputs, web results, and model reasoning are viewable by anyone with access to this repository via the AI gateway.</sub> </details> <sub>Advisory — never a merge gate. Disagree with a finding? Reply on it, or use the finding board under this review. Transcript `rv-20260806165959-096542`.</sub>
Owner

Superseded by #29. This PR was a pre-2026-08-06 Renovate artifact: it bumped package.json but could not rewrite the lockfile (the github.com toolchain artifactError fixed by our own Renovate image, forgejo-stack PR #36) — merging it as-is would have desynced the lockfile.

Superseded by #29. This PR was a pre-2026-08-06 Renovate artifact: it bumped package.json but could not rewrite the lockfile (the github.com toolchain artifactError fixed by our own Renovate image, forgejo-stack PR #36) — merging it as-is would have desynced the lockfile.
A-Guevara closed this pull request 2026-08-12 16:21:13 +00:00
Some checks failed
renovate/artifacts Artifact file update failure
baseline-security / baseline (push) Failing after 1m38s
scan / trivy-fs (push) Failing after 1m52s
ai-review / review (pull_request) Successful in 3m6s
baseline-security / baseline (pull_request) Failing after 1m38s
Required
Details
scan / trivy-fs (pull_request) Failing after 1m13s

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
tti/tti-ux!22
No description provided.