[security] deps: vulnerability in brace-expansion (high) #103

Closed
opened 2026-07-28 22:02:48 +00:00 by maximus · 2 comments
Owner

Détection

Détecté par le Défenseur defenseur-simpl-liste le 2026-07-27T05:15:10.727Z. Confirmé par npm audit live sur master.

Findings

  • [HIGH] defenseur-simpl-liste-deps-npm-audit-simpl-liste-brace-expansion
    • GHSA-3jxr-9vmj-r5cpDoS via exponential-time expansion of consecutive non-expanding {} groups (range >=2.0.0 <2.1.2)
    • GHSA-mh99-v99m-4gvgDoS via unbounded expansion length causing OOM crash (range <=5.0.7)

Trois majeurs cohabitent dans l'arbre, tirés par des minimatch différents :

Version installée Parent Chemin
1.1.13 minimatch 3.x glob@7 > minimatch@3 > brace-expansion (react-native codegen, @expo/cli dev-middleware, babel-jest)
2.1.0 minimatch 9.x @expo/cli > minimatch@9 > brace-expansion
5.0.6 minimatch 10.x @expo/fingerprint > minimatch@10 > brace-expansion

Surface runtime : build/dev-time uniquement — ce package n'est pas bundlé dans l'APK livré. Sévérité effective = hardening (garder npm audit à 0), pas un incident runtime.

Résolution suggérée

Non trivial — la CVE OOM (<=5.0.7) n'a de fix qu'en 5.0.8 ; il n'y a pas de patch backporté en 1.x ni 2.x. Deux options :

  1. Override blanket "brace-expansion": "^5.0.8"à valider pour régression : il écraserait les instances 1.x/2.x, or minimatch 3.x attend brace-expansion@^1.x. Tester le dev server + build EAS avant de merger.
  2. Attendre l'amont — laisser minimatch/glob bumper leur borne, puis re-résoudre. Vu la surface build-time nulle, l'attente est acceptable.

npm audit rapporte fixAvailable: true (non-major), mais tracer l'arbre (npm ls brace-expansion) avant tout override blanket — cf. gotcha « override scope vs blanket ».


Issue créée par /analyse-vulnerabilite depuis le rapport du Défenseur defenseur-simpl-liste (2026-07-27T05:15:10.727Z), réconcilié avec npm audit live sur master. Le Défenseur ne porte pas encore l'état accepted (le rapport est régénéré à chaque scan, éditer le JSON est un no-op). Pour un faux positif : fermer l'issue avec un commentaire d'analyse. Pour une vraie cause racine côté scanner : ouvrir une issue dans agent-defenseurs.

## Détection Détecté par le Défenseur **defenseur-simpl-liste** le 2026-07-27T05:15:10.727Z. Confirmé par `npm audit` live sur `master`. ## Findings - **[HIGH]** `defenseur-simpl-liste-deps-npm-audit-simpl-liste-brace-expansion` - [GHSA-3jxr-9vmj-r5cp](https://github.com/advisories/GHSA-3jxr-9vmj-r5cp) — *DoS via exponential-time expansion of consecutive non-expanding {} groups* (range `>=2.0.0 <2.1.2`) - [GHSA-mh99-v99m-4gvg](https://github.com/advisories/GHSA-mh99-v99m-4gvg) — *DoS via unbounded expansion length causing OOM crash* (range `<=5.0.7`) Trois majeurs cohabitent dans l'arbre, tirés par des `minimatch` différents : | Version installée | Parent | Chemin | |---|---|---| | `1.1.13` | minimatch 3.x | `glob@7 > minimatch@3 > brace-expansion` (react-native codegen, @expo/cli dev-middleware, babel-jest) | | `2.1.0` | minimatch 9.x | `@expo/cli > minimatch@9 > brace-expansion` | | `5.0.6` | minimatch 10.x | `@expo/fingerprint > minimatch@10 > brace-expansion` | > **Surface runtime** : build/dev-time uniquement — ce package n'est pas bundlé dans l'APK livré. Sévérité effective = **hardening** (garder `npm audit` à 0), pas un incident runtime. ## Résolution suggérée **Non trivial** — la CVE OOM (`<=5.0.7`) n'a de fix qu'en **5.0.8** ; il n'y a pas de patch backporté en 1.x ni 2.x. Deux options : 1. **Override blanket** `"brace-expansion": "^5.0.8"` — **à valider pour régression** : il écraserait les instances 1.x/2.x, or minimatch 3.x attend `brace-expansion@^1.x`. Tester le dev server + build EAS avant de merger. 2. **Attendre l'amont** — laisser `minimatch`/`glob` bumper leur borne, puis re-résoudre. Vu la surface build-time nulle, l'attente est acceptable. `npm audit` rapporte `fixAvailable: true` (non-major), mais tracer l'arbre (`npm ls brace-expansion`) avant tout override blanket — cf. gotcha « override scope vs blanket ». --- _Issue créée par `/analyse-vulnerabilite` depuis le rapport du Défenseur **defenseur-simpl-liste** (2026-07-27T05:15:10.727Z), réconcilié avec `npm audit` live sur `master`. Le Défenseur ne porte pas encore l'état `accepted` (le rapport est régénéré à chaque scan, éditer le JSON est un no-op). Pour un **faux positif** : fermer l'issue avec un commentaire d'analyse. Pour une **vraie cause racine côté scanner** : ouvrir une issue dans `agent-defenseurs`._
maximus added the
type:security
source:defenseur
labels 2026-07-28 22:02:48 +00:00
Author
Owner

Analyse (/analyze) — accepté comme non corrigeable in-place

Exploration des sites d'appel réels : l'override blanket brace-expansion: ^5.0.8 est cassant, et aucun fix ne met npm audit à 0 sans risque.

Preuve — rupture de contrat CJS entre majeurs

Instance installée minimatch (parent) Contrat brace-expansion
1.1.13 ×4 @3.1.5 (glob@7, @react-native/codegen, rimraf@3, test-exclude) module.exports = expandTopexport défaut
2.1.0 @9.0.9 (@expo/cli) module.exports = expandTopexport défaut
5.0.6 @10.2.5 (@expo/fingerprint) exports.expand, __esModule:trueexport nommé
  • minimatch@3 : var expand = require('brace-expansion'); expand(pattern) → avec 5.x, require() renvoie {expand, __esModule}TypeError: expand is not a function.
  • minimatch@9 : __importDefault(require("brace-expansion")).default(pattern) → 5.x a __esModule:true.default est undefinedTypeError.
  • Un blanket ^5.0.8 casserait donc le bundling (expo start) : chemin react-native + @react-native/codegen (Metro/codegen), glob/rimraf, @expo/cli.

Pourquoi aucun fix propre

  • CVE OOM (GHSA-mh99-v99m-4gvg, <=5.0.7) corrigée seulement en 5.0.8, aucun backport 1.x/2.x. 1.1.13/2.1.0 sont déjà les têtes de leur majeur (patchées pour la ReDoS antérieure) → non remontables in-place sans saut de majeur cassant.
  • Override scopé @expo/fingerprint > brace-expansion ^5.0.8 (minimatch@10, export nommé → compatible) corrige la seule instance 5.x mais laisse 1.1.13/2.1.0npm audit reste rouge. Valeur limitée.
  • Bumper les parents (minimatch@10 / glob) : minimatch@10 pas API-compatible avec glob@7/rimraf@3, blast radius élevé.

Surface réelle

Les 3 instances sont dev/build-time (glob/rimraf/test-exclude/@expo/cli fingerprint), opérant sur des chemins de fichiers locaux de confiance. L'OOM n'est pas déclenchable par un tiers externe → surface réelle nulle dans le modèle de menace de l'app livrée (cohérent avec le raisonnement « dev-tooling-only » de #100).

Décision

Accepté comme hardening différé — fermé. À ré-évaluer quand glob/rimraf/minimatch bumperont brace-expansion en amont. Le Défenseur re-remontera ce finding à chaque scan (l'état accepted n'existe pas encore côté scanner) — c'est attendu, pas une régression.

Précédent : #100 a splitté js-yaml en 2 scopes (^4/^3) plutôt qu'un blanket pour ce même cas « deux majeurs incompatibles ».

## Analyse (/analyze) — accepté comme non corrigeable in-place Exploration des **sites d'appel réels** : l'override blanket `brace-expansion: ^5.0.8` est **cassant**, et aucun fix ne met `npm audit` à 0 sans risque. ### Preuve — rupture de contrat CJS entre majeurs | Instance installée | minimatch (parent) | Contrat brace-expansion | |---|---|---| | `1.1.13` ×4 | @3.1.5 (`glob@7`, `@react-native/codegen`, `rimraf@3`, `test-exclude`) | `module.exports = expandTop` — **export défaut** | | `2.1.0` | @9.0.9 (`@expo/cli`) | `module.exports = expandTop` — **export défaut** | | `5.0.6` | @10.2.5 (`@expo/fingerprint`) | `exports.expand`, `__esModule:true` — **export nommé** | - minimatch@3 : `var expand = require('brace-expansion'); expand(pattern)` → avec 5.x, `require()` renvoie `{expand, __esModule}` → **`TypeError: expand is not a function`**. - minimatch@9 : `__importDefault(require("brace-expansion")).default(pattern)` → 5.x a `__esModule:true` → `.default` est `undefined` → **`TypeError`**. - Un blanket `^5.0.8` casserait donc le bundling (`expo start`) : chemin `react-native` + `@react-native/codegen` (Metro/codegen), `glob`/`rimraf`, `@expo/cli`. ### Pourquoi aucun fix propre - **CVE OOM** ([GHSA-mh99-v99m-4gvg](https://github.com/advisories/GHSA-mh99-v99m-4gvg), `<=5.0.7`) corrigée **seulement en 5.0.8**, aucun backport 1.x/2.x. `1.1.13`/`2.1.0` sont déjà les têtes de leur majeur (patchées pour la ReDoS antérieure) → non remontables in-place sans saut de majeur cassant. - **Override scopé** `@expo/fingerprint > brace-expansion ^5.0.8` (minimatch@10, export nommé → compatible) corrige la seule instance 5.x mais laisse `1.1.13`/`2.1.0` → **`npm audit` reste rouge**. Valeur limitée. - **Bumper les parents** (minimatch@10 / glob) : minimatch@10 pas API-compatible avec `glob@7`/`rimraf@3`, blast radius élevé. ### Surface réelle Les 3 instances sont **dev/build-time** (`glob`/`rimraf`/`test-exclude`/`@expo/cli fingerprint`), opérant sur des chemins de fichiers **locaux de confiance**. L'OOM n'est pas déclenchable par un tiers externe → **surface réelle nulle** dans le modèle de menace de l'app livrée (cohérent avec le raisonnement « dev-tooling-only » de #100). ### Décision **Accepté comme hardening différé — fermé.** À ré-évaluer quand `glob`/`rimraf`/`minimatch` bumperont `brace-expansion` en amont. Le Défenseur re-remontera ce finding à chaque scan (l'état `accepted` n'existe pas encore côté scanner) — c'est attendu, pas une régression. Précédent : #100 a splitté `js-yaml` en 2 scopes (`^4`/`^3`) plutôt qu'un blanket pour ce même cas « deux majeurs incompatibles ».
maximus reopened this issue 2026-08-12 18:06:37 +00:00
maximus added the
status:ready
label 2026-08-12 18:06:38 +00:00
Author
Owner

Rouverte -- debloquee par la technique version-scoped

Le verdict initial (non fixable in-place) tenait pour un override blanket brace-expansion: ^5.0.8 (qui force 5.x sur minimatch@3/@9 attendant l'export par defaut -> TypeError au bundling) et pour l'override scope partiel @expo/fingerprint (incomplet).

La solution manquee : des overrides version-scoped par majeur, qui bumpent chaque majeur DANS son majeur, preservant le contrat d'API :

"brace-expansion@^1.0.0": "^1.1.18",
"brace-expansion@^2.0.0": "^2.1.4",
"brace-expansion@^5.0.0": "^5.0.9"

Les tetes patchees existent desormais dans chaque majeur (1.1.18, 2.1.4, 5.0.9). Resultat : 1.1.13->1.1.18 (x4, export defaut preserve), 2.1.0->2.1.4 (export defaut), 5.0.6->5.0.9 (export nomme). Aucun invalid.

Verifie : minimatch@3 (celui qui levait TypeError sous blanket) resout correctement -- mm("src/foo.js","src/*.js") == true. Smoke test 11/11. Fix dans la PR a venir.

## Rouverte -- debloquee par la technique version-scoped Le verdict initial (non fixable in-place) tenait pour un override **blanket** `brace-expansion: ^5.0.8` (qui force 5.x sur minimatch@3/@9 attendant l'export par defaut -> `TypeError` au bundling) et pour l'override scope partiel `@expo/fingerprint` (incomplet). La solution manquee : des overrides **version-scoped par majeur**, qui bumpent chaque majeur DANS son majeur, preservant le contrat d'API : ```jsonc "brace-expansion@^1.0.0": "^1.1.18", "brace-expansion@^2.0.0": "^2.1.4", "brace-expansion@^5.0.0": "^5.0.9" ``` Les tetes patchees existent desormais dans chaque majeur (1.1.18, 2.1.4, 5.0.9). Resultat : `1.1.13->1.1.18` (x4, export defaut preserve), `2.1.0->2.1.4` (export defaut), `5.0.6->5.0.9` (export nomme). Aucun `invalid`. **Verifie** : `minimatch@3` (celui qui levait TypeError sous blanket) resout correctement -- `mm("src/foo.js","src/*.js") == true`. Smoke test 11/11. Fix dans la PR a venir.
maximus added
status:approved
and removed
status:ready
labels 2026-08-12 18:13:06 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
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: maximus/simpl-liste#103
No description provided.