fix(security): drop image-size via metro 0.83.8 overrides #114

Merged
maximus merged 1 commit from issue-108-metro-0.83.8 into master 2026-09-04 20:16:41 +00:00
Owner

Lève la seule racine non corrigeable de l'arbre de dépendances, suivie dans #108.

Ce qui a débloqué la situation

#108 attendait qu'image-size publie une version saine. Ce n'est toujours pas arrivé — vérifié à la source (api.github.com/advisories) :

Advisory Range vulnérable first_patched_version
GHSA-w3rx-r6r6-pgpr (ICNS) <= 2.0.2 None
GHSA-5p2g-fcmc-qvqq (JXL/HEIF) <= 2.0.2 None

image-size@2.0.2 (2025-04-02) reste la dernière publiée. Sur ce point, l'analyse de #108 tient toujours : aucun override d'image-size n'a de cible saine.

Le déblocage vient d'ailleurs : metro a retiré la dépendance au lieu d'attendre le correctif upstream, et l'a backporté sur la ligne de maintenance 0.83.x le 2026-08-19 — sept jours après la dernière ré-évaluation de #108.

metro@0.83.7  2026-04-29  image-size: ^1.0.2
metro@0.83.8  2026-08-19  image-size: ABSENTE

Diff de dépendances 0.83.7 → 0.83.8 : une seule suppression, image-size, zéro ajout.

Metro a vendoré le calcul de dimensions dans src/lib/imageSize.js (« derived from image-size's format support, reduced »). Le code vendoré ne gère que bmp, gif, jpeg, ktx, png, psd, svg, tiff, webp — aucun parseur ICNS, JXL ni HEIF, soit exactement les trois visés par les advisories. La vulnérabilité disparaît par construction, pas par renommage.

metro@0.83.3 étant le seul demandeur d'image-size dans tout l'arbre, les findings HIGH transitifs s'effondrent avec elle.

Pourquoi un override

@expo/metro@54.2.0 épingle les 14 paquets metro en version exacte 0.83.3 — npm update ne peut rien. Les 14 sont déplacés ensemble pour éviter un arbre metro panaché (metro 0.83.8 cohabitant avec des metro-* en 0.83.3). L'override ws existant est préservé via la clé "." :

"metro": { "ws": "^7.5.11", ".": "^0.83.8" }

Vérifications

Vérification Résultat
npm audit 19 → 11 total, 9 HIGH → 1 HIGH
image-size dans l'arbre aucune instance (ni lock, ni node_modules)
Arbre metro 14 paquets en 0.83.8, un seul exemplaire chacun
npm install + postinstall patch-package react-native-android-widget@0.21.0 ✔
npm test 11/11 pass
npx expo export --platform android bundle Hermes 8.17 MB, PNG traités avec variations de dimensions — le chemin de code qui utilisait image-size

engines de metro 0.83.8 : node >=20.19.4, identique à 0.83.3 — aucun relèvement de plancher. EAS installe depuis le lockfile, l'override est honoré en build.

Hors périmètre

La HIGH résiduelle est browserslist (croissance mémoire non bornée + écriture de prototype via browserslist-stats.json). Elle préexiste sur master, sans lien avec #108, et n'est couverte par aucune issue ouverte. Les 9 moderate et 1 low sont inchangés — aucune régression.

Closes #108

🤖 Generated with Claude Code

https://claude.ai/code/session_016Y4GzEsmDryrwtQcmBNw9P

Lève la seule racine non corrigeable de l'arbre de dépendances, suivie dans #108. ## Ce qui a débloqué la situation #108 attendait qu'`image-size` publie une version saine. **Ce n'est toujours pas arrivé** — vérifié à la source (`api.github.com/advisories`) : | Advisory | Range vulnérable | `first_patched_version` | |---|---|---| | GHSA-w3rx-r6r6-pgpr (ICNS) | `<= 2.0.2` | **None** | | GHSA-5p2g-fcmc-qvqq (JXL/HEIF) | `<= 2.0.2` | **None** | `image-size@2.0.2` (2025-04-02) reste la dernière publiée. Sur ce point, l'analyse de #108 tient toujours : aucun override d'`image-size` n'a de cible saine. Le déblocage vient d'ailleurs : **metro a retiré la dépendance** au lieu d'attendre le correctif upstream, et l'a backporté sur la ligne de maintenance `0.83.x` le **2026-08-19** — sept jours après la dernière ré-évaluation de #108. ``` metro@0.83.7 2026-04-29 image-size: ^1.0.2 metro@0.83.8 2026-08-19 image-size: ABSENTE ``` Diff de dépendances `0.83.7 → 0.83.8` : **une seule suppression, `image-size`**, zéro ajout. Metro a vendoré le calcul de dimensions dans `src/lib/imageSize.js` (« derived from image-size's format support, *reduced* »). Le code vendoré ne gère que `bmp, gif, jpeg, ktx, png, psd, svg, tiff, webp` — **aucun parseur ICNS, JXL ni HEIF**, soit exactement les trois visés par les advisories. La vulnérabilité disparaît par construction, pas par renommage. `metro@0.83.3` étant le **seul** demandeur d'`image-size` dans tout l'arbre, les findings HIGH transitifs s'effondrent avec elle. ## Pourquoi un override `@expo/metro@54.2.0` épingle les 14 paquets metro en version **exacte** `0.83.3` — `npm update` ne peut rien. Les 14 sont déplacés ensemble pour éviter un arbre metro panaché (metro 0.83.8 cohabitant avec des `metro-*` en 0.83.3). L'override `ws` existant est préservé via la clé `"."` : ```json "metro": { "ws": "^7.5.11", ".": "^0.83.8" } ``` ## Vérifications | Vérification | Résultat | |---|---| | `npm audit` | **19 → 11** total, **9 HIGH → 1 HIGH** | | `image-size` dans l'arbre | aucune instance (ni lock, ni `node_modules`) | | Arbre metro | 14 paquets en 0.83.8, un seul exemplaire chacun | | `npm install` + postinstall | patch-package `react-native-android-widget@0.21.0` ✔ | | `npm test` | 11/11 pass | | `npx expo export --platform android` | bundle Hermes 8.17 MB, PNG traités avec variations de dimensions — le chemin de code qui utilisait `image-size` | `engines` de metro 0.83.8 : `node >=20.19.4`, identique à 0.83.3 — aucun relèvement de plancher. EAS installe depuis le lockfile, l'override est honoré en build. ## Hors périmètre La HIGH résiduelle est `browserslist` (croissance mémoire non bornée + écriture de prototype via `browserslist-stats.json`). Elle **préexiste sur master**, sans lien avec #108, et n'est couverte par aucune issue ouverte. Les 9 moderate et 1 low sont inchangés — aucune régression. Closes #108 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016Y4GzEsmDryrwtQcmBNw9P
maximus added the
status:review
type:security
labels 2026-09-04 19:58:25 +00:00
maximus added 1 commit 2026-09-04 19:58:25 +00:00
metro@0.83.8 (published 2026-08-19) removed the image-size dependency and
vendored a reduced dimension parser (src/lib/imageSize.js) that supports
only bmp/gif/jpeg/ktx/png/psd/svg/tiff/webp. The ICNS, JXL and HEIF
parsers named by GHSA-w3rx-r6r6-pgpr and GHSA-5p2g-fcmc-qvqq are absent
from it, so the DoS is eliminated by construction rather than by an
upstream patch: image-size still publishes no fixed version (latest is
2.0.2, and both advisories report first_patched_version = None).

metro@0.83.3 was the only requester of image-size in the whole tree, so
every transitive HIGH finding collapses with it.

@expo/metro@54.2.0 pins the 14 metro packages to exact 0.83.3, so npm
update cannot move them and an override is required. All 14 are moved
together to avoid a mixed metro tree; the existing nested ws override is
preserved through the "." key.

npm audit: 19 -> 11 total, 9 HIGH -> 1 HIGH. The remaining HIGH
(browserslist) is unrelated and pre-existing on master.

Verified: image-size absent from both the lock file and node_modules,
14 metro packages resolved to 0.83.8 with no duplicates, patch-package
postinstall still applies, npm test 11/11, and expo export --platform
android produces a Hermes bundle with PNG dimension variations resolved
through the vendored parser.

Refs #108

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Y4GzEsmDryrwtQcmBNw9P
Author
Owner

Verdict : APPROVE

Chaque affirmation du corps de la PR a été reproduite indépendamment — audit, arbre de dépendances, lockfile, tests, export et serveur de dev (surface que le corps ne couvrait pas). La seule régression trouvée, trois points d'entrée @expo/metro qui ne résolvent plus, n'a aucun consommateur dans l'arbre : latente, non bloquante.

Vérifié à la source

Vérification Méthode Résultat
image-size absent jq sur le lock + ls node_modules aucune instance
metro seul demandeur sur master lock de master rejoué en scratchpad node_modules/metro requires image-size ^1.0.2 — unique
Parseur vendoré sans ICNS/JXL/HEIF lecture de metro/src/lib/imageSize.js bmp, gif, jpeg, jpg, ktx, png, psd, svg, tiff, webp — les 3 formats visés par les advisories sont absents
audit master (baseline) npm audit --package-lock-only sur le lock de master 19 total / 9 HIGH
audit PR npm audit 11 total / 1 HIGH
8 des 9 HIGH enracinées dans image-size champ .via des advisories master metro, metro-config, metro-transform-worker, @expo/metro, @expo/metro-config, @expo/cli, expo — tous via image-size
HIGH résiduelle hors périmètre audit PR browserslist seul, présent aussi sur master
Arbre metro cohérent jq sur le lock 14 paquets + ob1 en 0.83.8, un exemplaire chacun, aucun 0.83.3 résolu
Override accepté par npm npm ls metro exit 0, overridden, aucun invalid
Override ws préservé lock + package.json de metro metro/node_modules/ws → 7.5.11, et metro 0.83.8 déclare toujours ws ^7.5.10 — pas de saut majeur
engines inchangé tarballs 0.83.3 / 0.83.8 node >=20.19.4 des deux côtés
Lock en phase avec package.json régénération en scratchpad puis diff identique — le npm ci d'EAS honore bien l'override
npm test exécution 11/11
postinstall patch-package exécution react-native-android-widget@0.21.0 ✔
expo export --platform android exécution, sortie en scratchpad exit 0, bundle Hermes 8.17 MB, PNG « 4 variations » résolus
Serveur de dev expo start sur port dédié (8391, vérifié libre, PID de l'écouteur confirmé) + curl /index.bundle HTTP 200, 4624 modules, 6.0 MB
Secrets / provenance scan du diff rien ; toutes les resolved sur registry.npmjs.org

La preuve par mutation prend ici la forme inverse de l'habituelle : rejouer le lock de master fait remonter les 8 findings enracinées dans image-size (9 HIGH au total), le lock de la PR n'en laisse qu'une. Le finding était réel et il disparaît par construction.

Finding — trois points d'entrée @expo/metro ne résolvent plus (non bloquant)

@expo/metro@54.2.0 est un pur jeu de ré-exports écrit contre 0.83.3. Trois de ses fichiers visent des chemins privés que metro a déplacés ou supprimés entre 0.83.3 et 0.83.8 :

Shim @expo/metro Cible 0.83.3 0.83.8
metro-resolver/utils/toPosixPath metro-resolver/private/utils/toPosixPath présent supprimé
metro-file-map/lib/dependencyExtractor metro-file-map/private/lib/dependencyExtractor présent déplacé vers src/plugins/dependencies/
metro/node-haste/Package metro/private/node-haste/Package présent supprimé (seul PackageCache subsiste)

Les trois lèvent MODULE_NOT_FOUND à l'exécution sur la branche (vérifié par require direct).

Ce qui les rend non bloquants : aucun paquet de l'arbre ne les requiert. Grep exhaustif sur tout node_modules, tous types de fichiers, à la fois sur la forme @expo/metro/<sous-chemin> et sur le spécificateur nu metro-*/private/... — les seuls fichiers qui les mentionnent sont les shims eux-mêmes. @expo/cli importe bien @expo/metro/metro-resolver, mais le shim racine, qui résout ; jamais le sous-chemin utils/toPosixPath. Sur les 206 imports profonds metro*/private/... présents dans l'arbre, 203 résolvent — ces 3 sont les seuls cassés, et ils sont morts.

Note méthodologique : private/* est un alias de la table exports vers src/*.js. Un premier passage qui cherchait un répertoire private/ littéral dans les tarballs a conclu « absent des deux côtés », donc « préexistant » — faux. C'est la résolution via la table exports qui donne le vrai delta 0.83.3 → 0.83.8.

Ce que ça dit quand même : 0.83.3 → 0.83.8 n'est pas un bump de patch pur côté API privée, malgré le numéro de version. Le risque résiduel est qu'un futur patch d'expo/@expo/cli se mette à charger un de ces sous-chemins. Une ligne dans #108 ou dans STATE.md éviterait que ce soit une énigme dans six mois.

Notes, aucune action requise

  1. Écart hermes-parser élargi. metro passe de 0.32.0 à 0.35.0 pendant que @react-native/codegen, @expo/metro-config et babel-plugin-syntax-hermes-parser restent sur 0.29.1. L'écart préexiste sur master (0.32 contre 0.29) — la PR l'élargit sans le créer. L'AST produit par metro-babel-transformer traverse ensuite le preset Babel de RN 0.81 ; export et serveur de dev sortent tous deux un bundle valide sur 4624 modules, ce qui couvre la syntaxe réelle du projet.

  2. chalk disparaît aussi des dépendances de metro entre 0.83.3 et 0.83.8. Le corps ne mentionne qu'image-size parce qu'il compare 0.83.7 → 0.83.8 ; sur 0.83.3 → 0.83.8 le retrait est double. Sans effet.

  3. ^0.83.8 autorise la dérive jusqu'à <0.84.0. Le lock est commité et EAS installe par npm ci : pas de risque pratique, et c'est cohérent avec le style des overrides déjà en place.

  4. Mot-clé de fermeture. Le commit porte Refs #108, le corps de la PR porte Closes #108. Forgejo lit les deux au merge, donc #108 se fermera bien par le corps. Simple asymétrie, rien à corriger.


🤖 Review générée par /pr-review

## Verdict : APPROVE Chaque affirmation du corps de la PR a été reproduite indépendamment — audit, arbre de dépendances, lockfile, tests, export **et serveur de dev** (surface que le corps ne couvrait pas). La seule régression trouvée, trois points d'entrée `@expo/metro` qui ne résolvent plus, n'a aucun consommateur dans l'arbre : latente, non bloquante. ### Vérifié à la source | Vérification | Méthode | Résultat | |---|---|---| | `image-size` absent | `jq` sur le lock + `ls node_modules` | aucune instance | | metro seul demandeur sur master | lock de `master` rejoué en scratchpad | `node_modules/metro requires image-size ^1.0.2` — unique | | Parseur vendoré sans ICNS/JXL/HEIF | lecture de `metro/src/lib/imageSize.js` | `bmp, gif, jpeg, jpg, ktx, png, psd, svg, tiff, webp` — les 3 formats visés par les advisories sont absents | | audit `master` (baseline) | `npm audit --package-lock-only` sur le lock de master | **19 total / 9 HIGH** | | audit PR | `npm audit` | **11 total / 1 HIGH** | | 8 des 9 HIGH enracinées dans `image-size` | champ `.via` des advisories master | `metro`, `metro-config`, `metro-transform-worker`, `@expo/metro`, `@expo/metro-config`, `@expo/cli`, `expo` — tous `via image-size` | | HIGH résiduelle hors périmètre | audit PR | `browserslist` seul, présent aussi sur master | | Arbre metro cohérent | `jq` sur le lock | 14 paquets + `ob1` en 0.83.8, un exemplaire chacun, aucun 0.83.3 résolu | | Override accepté par npm | `npm ls metro` | exit 0, `overridden`, aucun `invalid` | | Override `ws` préservé | lock + `package.json` de metro | `metro/node_modules/ws → 7.5.11`, et metro 0.83.8 déclare toujours `ws ^7.5.10` — pas de saut majeur | | `engines` inchangé | tarballs 0.83.3 / 0.83.8 | `node >=20.19.4` des deux côtés | | Lock en phase avec `package.json` | régénération en scratchpad puis diff | **identique** — le `npm ci` d'EAS honore bien l'override | | `npm test` | exécution | 11/11 | | postinstall patch-package | exécution | `react-native-android-widget@0.21.0 ✔` | | `expo export --platform android` | exécution, sortie en scratchpad | exit 0, bundle Hermes 8.17 MB, PNG « 4 variations » résolus | | **Serveur de dev** | `expo start` sur port dédié (8391, vérifié libre, PID de l'écouteur confirmé) + `curl /index.bundle` | **HTTP 200, 4624 modules, 6.0 MB** | | Secrets / provenance | scan du diff | rien ; toutes les `resolved` sur `registry.npmjs.org` | La preuve par mutation prend ici la forme inverse de l'habituelle : rejouer le lock de `master` fait remonter les 8 findings enracinées dans `image-size` (9 HIGH au total), le lock de la PR n'en laisse qu'une. Le finding était réel et il disparaît par construction. ### Finding — trois points d'entrée `@expo/metro` ne résolvent plus (non bloquant) `@expo/metro@54.2.0` est un pur jeu de ré-exports écrit contre 0.83.3. Trois de ses fichiers visent des chemins privés que metro a déplacés ou supprimés entre 0.83.3 et 0.83.8 : | Shim `@expo/metro` | Cible | 0.83.3 | 0.83.8 | |---|---|---|---| | `metro-resolver/utils/toPosixPath` | `metro-resolver/private/utils/toPosixPath` | présent | **supprimé** | | `metro-file-map/lib/dependencyExtractor` | `metro-file-map/private/lib/dependencyExtractor` | présent | **déplacé** vers `src/plugins/dependencies/` | | `metro/node-haste/Package` | `metro/private/node-haste/Package` | présent | **supprimé** (seul `PackageCache` subsiste) | Les trois lèvent `MODULE_NOT_FOUND` à l'exécution sur la branche (vérifié par `require` direct). Ce qui les rend non bloquants : **aucun paquet de l'arbre ne les requiert.** Grep exhaustif sur tout `node_modules`, tous types de fichiers, à la fois sur la forme `@expo/metro/<sous-chemin>` et sur le spécificateur nu `metro-*/private/...` — les seuls fichiers qui les mentionnent sont les shims eux-mêmes. `@expo/cli` importe bien `@expo/metro/metro-resolver`, mais le shim racine, qui résout ; jamais le sous-chemin `utils/toPosixPath`. Sur les 206 imports profonds `metro*/private/...` présents dans l'arbre, 203 résolvent — ces 3 sont les seuls cassés, et ils sont morts. Note méthodologique : `private/*` est un alias de la table `exports` vers `src/*.js`. Un premier passage qui cherchait un répertoire `private/` littéral dans les tarballs a conclu « absent des deux côtés », donc « préexistant » — faux. C'est la résolution via la table `exports` qui donne le vrai delta 0.83.3 → 0.83.8. Ce que ça dit quand même : **0.83.3 → 0.83.8 n'est pas un bump de patch pur côté API privée**, malgré le numéro de version. Le risque résiduel est qu'un futur patch d'`expo`/`@expo/cli` se mette à charger un de ces sous-chemins. Une ligne dans #108 ou dans `STATE.md` éviterait que ce soit une énigme dans six mois. ### Notes, aucune action requise 1. **Écart `hermes-parser` élargi.** metro passe de 0.32.0 à 0.35.0 pendant que `@react-native/codegen`, `@expo/metro-config` et `babel-plugin-syntax-hermes-parser` restent sur 0.29.1. L'écart **préexiste** sur master (0.32 contre 0.29) — la PR l'élargit sans le créer. L'AST produit par `metro-babel-transformer` traverse ensuite le preset Babel de RN 0.81 ; export et serveur de dev sortent tous deux un bundle valide sur 4624 modules, ce qui couvre la syntaxe réelle du projet. 2. **`chalk` disparaît aussi** des dépendances de metro entre 0.83.3 et 0.83.8. Le corps ne mentionne qu'`image-size` parce qu'il compare 0.83.7 → 0.83.8 ; sur 0.83.3 → 0.83.8 le retrait est double. Sans effet. 3. **`^0.83.8` autorise la dérive jusqu'à `<0.84.0`.** Le lock est commité et EAS installe par `npm ci` : pas de risque pratique, et c'est cohérent avec le style des overrides déjà en place. 4. **Mot-clé de fermeture.** Le commit porte `Refs #108`, le corps de la PR porte `Closes #108`. Forgejo lit les deux au merge, donc #108 se fermera bien par le corps. Simple asymétrie, rien à corriger. --- 🤖 Review générée par `/pr-review`
maximus merged commit eff15ca9b8 into master 2026-09-04 20:16:41 +00:00
Sign in to join this conversation.
No reviewers
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#114
No description provided.