Skip to main content

Différences de gestion des versions entre RubyGems et npm

Écrit par
Headshot of Gareth Visagie

Gareth Visagie

14 décembre 2016

0 minutes de lecture

RubyGems et npm sont les gestionnaires de paquets de référence pour Ruby et Node.js.

À première vue, ils semblent similaires (et ils le sont !), mais la création d’un produit qui interagit avec les deux nécessite de prendre en compte quelques différences subtiles.

Nous avons récemment ajouté la prise en charge des projets Ruby dans Snyk. Les différences de gestion des versions entre RubyGems et npm nous ont posé quelques difficultés en cours de route.

Cet article de blog décrit ces différences, les problèmes qu’elles ont entraînés et la façon dont nous les avons résolus.

Gestion des versions sémantiques

Les développeurs qui travaillent avec Node.js et Ruby interagissent principalement avec la norme de gestion des versions sémantiques par l’intermédiaire de ces gestionnaires de paquets. Il est donc facile de penser que la manière dont chaque gestionnaire de paquets gère les versions fait partie de SemVer. Comme nous allons le voir, ce n’est pas le cas.

Qu’est-ce que SemVer ?

La gestion des versions sémantiques (SemVer) est une norme de numérotation des versions logicielles qui vise à aider les utilisateurs de bibliothèques à comprendre l’impact des nouvelles versions, simplement en comparant les numéros de version.

Les logiciels qui respectent la norme SemVer doivent être identifiés par des numéros de version composés. Les modifications de chaque partie de ce numéro impliquent différents types de changements dans le logiciel lui-même.

Pour citer directement http://semver.org :

Pour un numéro de version MAJOR.MINOR.PATCH, incrémentez :1. la version MAJOR en cas de modification incompatible de l’API ;2. la version MINOR en cas d’ajout de fonctionnalités rétrocompatibles ;3. la version PATCH en cas de correction de bogues rétrocompatible.Des libellés supplémentaires pour les préversions et les métadonnées de build sont disponibles en extension du format MAJOR.MINOR.PATCH.

La norme SemVer définit également des règles de priorité, qui indiquent comment comparer les versions pour les classer.

Qu’est-ce qui ne relève pas de SemVer ?

La plupart des gestionnaires de paquets, voire tous, permettent de spécifier des « plages » de versions. Déclarer les dépendances logicielles sous forme de plages, par exemple >= 5.0 and < 6, permet d’utiliser les bibliothèques logicielles dans un plus grand nombre de situations.

Cette « spécification de plage » ne fait pas partie de SemVer, même si les règles de comparaison en font partie. Chaque gestionnaire de paquets peut donc utiliser sa propre syntaxe et ses propres règles de spécification des plages, par exemple >= 5, ~ 1.2, ^2.3.0, >5 || <2.

La syntaxe des plages de RubyGems et npm est très similaire (nous y reviendrons plus loin). Si vous utilisez souvent les deux, il est donc facile de croire que cette syntaxe fait partie de la norme, alors que ce n’est pas le cas.

Numéros de version dans npm et RubyGems

npm et RubyGems n’ont pas la même définition d’un numéro de version valide.

Dans npm, les versions doivent respecter strictement Semantic Versioning 2.0. Tout numéro de version qui ne précise pas les versions majeure, mineure et corrective est invalide.

RubyGems adopte une définition plus souple d’une version valide et autorise un nombre illimité de composants. Par exemple, 5.1.0.2.1.0 est parfaitement valide. Les auteurs de gems sont encouragés à respecter la norme de gestion des versions sémantiques, mais cela n’est pas vérifié par le système.

Malgré cela, les numéros de version à quatre composants sont assez courants dans l’écosystème RubyGems. Chez Snyk, nous en rencontrons souvent, car de nombreuses gems (notamment Rails et ses composants) utilisent le quatrième composant pour les correctifs de sécurité (par exemple, une vulnérabilité dans 5.0.0 est corrigée dans 5.0.0.1).

Plages de versions dans npm et RubyGems

RubyGems et npm proposent un ensemble similaire d’opérateurs pour spécifier les plages de versions.

La gestion des plages dans RubyGems fait partie de la bibliothèque standard de Ruby. Dans npm, elle est assurée par le package semver.

Syntaxe des plages simples

Les opérateurs de plage simples >, >=, <, >= et = se comportent de la même manière dans RubyGems et npm. RubyGems propose également l’opérateur !=, qui n’est pas pris en charge par npm.

Syntaxe avancée des plages

npm et RubyGems permettent tous deux d’exprimer de façon concise des plages complexes. npm propose un plus grand nombre d’opérateurs que RubyGems, ce qui permet de couvrir un éventail légèrement plus large de plages complexes.

Les deux proposent un opérateur ~>, qui spécifie des plages « pessimistes ». En gros, il signifie « accepter toute version à partir de celle-ci, jusqu’à une limite probablement sûre, comme l’indique semver ». Son comportement exact est toutefois légèrement différent dans RubyGems et npm.

RubyGems l’interprète comme « autoriser les versions supérieures qui ne modifient que le composant de version le moins significatif », tandis que npm l’interprète comme « autoriser uniquement les modifications du correctif si une version corrective est spécifiée ; sinon, autoriser uniquement les modifications de la version mineure ». Voici un exemple qui l’illustre :

plage

Interprétation de RubyGems

Interprétation de npm

_~> 1.4.3.4<_/code>

_= 1.4.3.4_ et _<1.4.4_

erreur, version invalide

_~> 1.4.3_

_= 1.4.3_ et _< 1.5_

_= 1.4.3_ et _< 1.5_

_~> 1.4_

_= 1.4_ et _< 2_

_=1.4.0_ et _< 1.5.0_

Quels problèmes cela a-t-il causés ?

Au départ, nous avons supposé que la gestion des versions de RubyGems était un surensemble de celle de npm. Nous avons donc utilisé le package semver pour les opérations sur les plages de versions de npm et de RubyGems.

Lors des tests alpha, nous avons obtenu des résultats surprenants. Presque tous les dépôts Ruby que nous avons testés semblaient extrêmement vulnérables, y compris des applications Rails entièrement à jour.

La comparaison des rapports de tests automatisés avec ceux créés manuellement a révélé de nombreux faux positifs (et quelques faux négatifs). Une analyse plus approfondie a montré que le problème venait des différences de sémantique des plages entre RubyGems et npm.

Le problème fondamental : une version qui respecte une plage donnée dans RubyGems ne respecte pas nécessairement la même plage dans npm.

Il nous fallait une autre approche.

Comment avons-nous résolu ce problème ?

Snyk a d’abord pris en charge uniquement npm. Notre code produit utilisait donc déjà le package semver de npm pour les opérations sur les versions et les plages.

Nous avons décidé de créer une bibliothèque similaire pour gérer les versions à la manière de RubyGems, avec la même API que le package semver de npm.

Notre système choisit ensuite laquelle de ces bibliothèques utiliser en fonction du gestionnaire de paquets employé dans le code que nous testons, surveillons et corrigeons.

Comment avons-nous implémenté ruby-semver ?

La gestion des versions de RubyGems fait partie de la bibliothèque standard de Ruby. Plutôt que de la réimplémenter nous-mêmes, nous avons choisi d’utiliser Opal pour transpiler en JavaScript les parties pertinentes de RubyGems.

Nous avons d’abord essayé de transpiler directement les classes à partir du code source Ruby. Cela s’est avéré impossible en raison des différences entre la prise en charge des expressions régulières en JavaScript et en Ruby.

Finalement, nous avons copié deux classes de la bibliothèque standard Ruby dans notre dépôt, modifié les expressions régulières pour les rendre compatibles avec JavaScript, puis ajouté un script pour transpiler ce code source Ruby en JavaScript.

Une fois le code transpilé opérationnel dans une REPL Node.js, nous avons ajouté un ensemble de tests solides pour l’API de type npm-semver souhaitée. Nous avons ensuite écrit l’adaptateur le plus léger possible entre l’API npm SemVer et le code RubyGems transpilé.

Cette approche a bien fonctionné, mais elle repose sur la qualité d’un transpileur Ruby vers JavaScript. Une implémentation manuelle du comportement aurait été possible, mais aurait demandé davantage d’efforts et, surtout, aurait pu omettre des cas limites moins évidents déjà gérés par RubyGems.

Conclusion

L’ajout de la prise en charge de Ruby dans Snyk a mis en évidence des difficultés intéressantes liées à la manière dont différents langages et gestionnaires de paquets gèrent les versions. Les plages de versions SemVer posent encore plus de problèmes, car elles ne sont pas définies du tout.

Comprendre ces difficultés nous a permis de mieux cerner SemVer et ses limites, et ainsi de renforcer les bases nécessaires à la prise en charge d’autres écosystèmes.

Si vous travaillez avec Ruby et Node.js, découvrez ruby-semver. Nous l’avons publié en open source, en espérant qu’il pourra être utile à d’autres.

Ajoutez également Snyk à vos projets Ruby. Nous ajoutons activement la prise en charge de nouveaux types de projets Ruby (y compris les gems) : restez à l’écoute.

Lancez-vous dans les compétitions Capture The Flag

Apprenez à résoudre des défis de capture du drapeau en regardant à la demande notre atelier virtuel d’initiation.

Publié dans:

Lire la suite

blog feature snyk container custom base recommendations
Blog

Découvrez Evo Agentic AppSec : une sécurité au rythme du développement de l’IA

Le développement piloté par l’IA dépasse les workflows de sécurité traditionnels. L’AppSec agentique de Snyk s’appuie sur des agents de sécurité pour détecter, corriger et vérifier les vulnérabilités en continu, tandis que les équipes définissent les exigences.

feature insights context
Blog

Les attaques autonomes sont déjà là. La défense doit suivre leur rythme.

Les attaquants autonomes réduisent la fenêtre de défense. Découvrez comment la découverte, la correction, la validation et la prévention continues peuvent aider les équipes de sécurité à suivre le rythme.

feature insights context
Blog

La prévention est-elle essentiellement un problème résolu ?

La prévention dans le code généré par les agents est résolue sur le plan architectural, mais le défi reste de choisir des contrôles qui protègent la sécurité sans ralentir le développement.