Sécuriser les dépendances npm, PyPI et Go
Mise à jour :
En 2024-2025, PyPI et npm ont retiré des milliers de packages malveillants. Le vecteur d’attaque ? Des noms de packages ressemblant à des packages légitimes (typosquatting) ou exploitant la confusion entre registres publics et privés (dependency confusion).
Ce guide vous donne les outils pour éviter d’installer du code malveillant dans vos projets.
Les vecteurs d’attaque
Typosquatting
Le typosquatting exploite les erreurs de frappe lors de l’installation. L’attaquant publie un package dont le nom ressemble à un package populaire, en espérant que des développeurs se trompent.
Exemples de pièges courants
| Package légitime | Typosquat | Différence |
|---|---|---|
lodash | lodahs | Inversion de lettres |
requests | request | Singulier/pluriel |
tensorflow | tensorfow | Lettre manquante |
@angular/core | angular-core | Format différent |
Comment l’attaque se produit
Vous voulez installer requests (le client HTTP Python le plus populaire) :
# Vous tapez trop vite...pip install request # ❌ Typosquat ! Package malveillant
# Au lieu de :pip install requests # ✅ Le vrai packageLe package malveillant request (sans “s”) contient un script qui s’exécute
à l’installation. En quelques secondes, vos credentials AWS ou vos clés SSH
sont exfiltrés vers un serveur de l’attaquant.
Le plus vicieux : l’installation semble fonctionner normalement. Le package malveillant inclut souvent le vrai code du package légitime pour ne pas éveiller les soupçons.
Dependency confusion
La dependency confusion exploite un comportement par défaut des gestionnaires de packages : quand un package existe sur plusieurs registres, c’est la version la plus récente qui gagne.
Comment ça marche ?
Imaginons une entreprise fictive “Acme Corp” :
-
L’entreprise crée un package interne :
@acme/auth-utilsversion1.2.0, hébergé sur leur registre privé Artifactory. -
Un attaquant découvre ce nom (via une fuite de
package.json, un message d’erreur, ou du code source public). -
L’attaquant publie sur npm public :
@acme/auth-utilsversion99.0.0avec un scriptpostinstallmalveillant. -
Un développeur fait
npm install: npm voit deux sources pour le même package. Sans configuration explicite, il choisit la version99.0.0(plus récente) du registre public. -
Le script malveillant s’exécute : vol de credentials, backdoor installée.
Pourquoi ça fonctionne ?
| Comportement par défaut | Conséquence |
|---|---|
| npm/pip cherchent d’abord sur le registre public | Le package malveillant est trouvé |
| La version la plus haute est préférée | 99.0.0 > 1.2.0 |
| Les scripts s’exécutent automatiquement | Code malveillant lancé à l’install |
Comment se protéger ?
La solution est de forcer la résolution vers le registre privé pour vos packages internes. Voir les sections “Registre privé” plus bas pour npm, pip et Go.
Scripts d’installation malveillants
Les gestionnaires de packages permettent d’exécuter du code à l’installation :
| Gestionnaire | Script | Risque |
|---|---|---|
| npm | postinstall | Exécution arbitraire |
| pip | setup.py | Code Python au install |
| Go | Pas de scripts | ✅ Plus sûr |
Protection npm
npm est l’écosystème le plus ciblé par les attaquants (2+ millions de packages). Voici les protections à mettre en place, de la plus simple à la plus robuste.
Désactiver les scripts d’installation
Les scripts postinstall sont le vecteur d’attaque principal. Quand vous faites
npm install, npm peut exécuter du code arbitraire défini par le mainteneur du
package. C’est pratique pour compiler du code natif, mais c’est aussi une porte
ouverte aux malwares.
Solution : désactiver les scripts par défaut.
Créez ou modifiez le fichier .npmrc à la racine de votre projet :
# .npmrc - Configuration npm du projetignore-scripts=trueCette ligne indique à npm de ne jamais exécuter les scripts lors de l’installation. Si vous avez déjà un projet, vous pouvez aussi installer ponctuellement sans scripts :
npm install --ignore-scriptsVérification : après installation, aucun message “Running postinstall” ne doit apparaître.
Vérifier avant installation avec npq
npq est un wrapper autour de npm install qui analyse le package avant de
l’installer. Il détecte les signaux d’alerte que vous ne verriez pas autrement.
# Installer npq globalement (une seule fois)npm install -g npqEnsuite, utilisez npq au lieu de npm install :
# Au lieu de : npm install lodashnpq install lodashCe que npq vérifie automatiquement :
| Vérification | Ce que ça détecte |
|---|---|
| Typosquatting | Nom similaire à un package populaire |
| Mainteneurs | Compte compromis ou nouveau mainteneur suspect |
| Scripts | Présence de postinstall ou scripts suspects |
| Âge | Package créé récemment (< 7 jours = suspect) |
| Popularité | Téléchargements anormalement bas |
Exemple de sortie suspecte :
⚠️ lodahs has mass typosquatting potential⚠️ lodahs has a preinstall script❌ Package installation blockedÉpingler les versions exactes
Par défaut, npm utilise des ranges de versions (^4.17.0 = “4.17.0 ou plus
récent compatible”). Le problème : si le mainteneur publie une version
malveillante 4.18.0, votre prochain npm install l’installera automatiquement.
Solution : épingler les versions exactes.
{ "dependencies": { "lodash": "4.17.21", "express": "4.18.2" }}| Notation | Signification | Risque |
|---|---|---|
"4.17.21" | Exactement cette version | ✅ Sûr |
"^4.17.0" | 4.17.0 à 4.x.x | ⚠️ Mises à jour auto |
"~4.17.0" | 4.17.0 à 4.17.x | ⚠️ Mises à jour mineures |
"*" | N’importe quelle version | ❌ Dangereux |
Configurer npm pour toujours épingler :
npm config set save-exact trueLockfile obligatoire
Le fichier package-lock.json enregistre les versions exactes de toutes vos
dépendances (directes et transitives) avec leurs checksums. C’est votre garantie
de reproductibilité.
Règle absolue : toujours commiter le lockfile.
git add package-lock.jsongit commit -m "chore: update lockfile"En production, utilisez npm ci au lieu de npm install :
# ❌ npm install : peut modifier le lockfilenpm install
# ✅ npm ci : respecte strictement le lockfilenpm ci| Commande | Comportement | Usage |
|---|---|---|
npm install | Peut mettre à jour les dépendances | Développement |
npm ci | Installe exactement ce qui est dans le lockfile | CI/CD, production |
npm ci est aussi plus rapide car il supprime node_modules et réinstalle
tout d’un coup au lieu de calculer les différences.
Registre privé (contre dependency confusion)
Si votre entreprise utilise des packages internes (ex: @acme/utils), vous
devez forcer npm à les chercher uniquement sur votre registre privé.
Créez un fichier .npmrc à la racine du projet :
# Tous les packages @acme/* viennent UNIQUEMENT du registre privé@acme:registry=https://npm.acme-corp.com/
# Le reste vient de npm publicregistry=https://registry.npmjs.org/Ce que ça change :
| Sans cette config | Avec cette config |
|---|---|
npm cherche @acme/utils sur npm public | npm cherche @acme/utils sur votre registre privé |
| Risque de dependency confusion | ✅ Impossible d’installer un faux package |
Vérification : tentez d’installer un package @acme/* inexistant. Vous
devez obtenir une erreur 404 de votre registre privé, pas de npm public.
Protection PyPI
PyPI (Python Package Index) est moins mature que npm en termes de sécurité.
Il n’y a pas de vérification d’identité des mainteneurs, et les packages peuvent
exécuter du code Python arbitraire à l’installation via setup.py.
Auditer les vulnérabilités avec pip-audit
pip-audit scanne vos dépendances installées et les compare à la base de données des vulnérabilités connues (OSV, PyPI Advisory Database).
# Installer pip-audit (une seule fois)pip install pip-auditScanner votre environnement actuel :
pip-auditExemple de sortie :
Found 2 known vulnerabilities in 1 packageName Version ID Fix Versions------- ------- ------------------- ------------requests 2.25.1 GHSA-j8r2-6x86-q33q 2.31.0requests 2.25.1 CVE-2023-32681 2.31.0Scanner un fichier requirements.txt (avant installation) :
pip-audit -r requirements.txtC’est utile en CI/CD pour bloquer un déploiement si une dépendance vulnérable est détectée.
Éviter l’exécution de code à l’installation
Quand vous installez un package Python, pip peut exécuter setup.py — un script
Python arbitraire. Les attaquants exploitent ce mécanisme pour exécuter du code
malveillant.
Solution : préférer les wheels (packages pré-compilés).
Un wheel (.whl) est un package déjà compilé. Il n’y a pas de setup.py à
exécuter, donc pas de risque d’exécution de code.
# Forcer l'installation uniquement via wheelspip install --only-binary :all: requests| Type de package | Extension | Exécute du code ? |
|---|---|---|
| Source (sdist) | .tar.gz | ⚠️ Oui (setup.py) |
| Wheel | .whl | ✅ Non |
Limitation : tous les packages n’ont pas de wheel disponible (notamment ceux avec du code C). Dans ce cas, l’installation échouera. Auditez manuellement ces packages avant de les autoriser.
Configurer le registre privé
Pour les packages internes, configurez pip pour utiliser votre registre privé.
Créez le fichier pip.conf :
- Linux :
~/.config/pip/pip.conf - macOS :
~/Library/Application Support/pip/pip.conf - Windows :
%APPDATA%\pip\pip.ini
[global]# Registre principal : votre registre privéindex-url = https://pypi.acme-corp.com/simple/
# Fallback sur PyPI public pour les packages open sourceextra-index-url = https://pypi.org/simple/Épingler avec hash (intégrité garantie)
Épingler une version (requests==2.31.0) ne suffit pas : un attaquant qui
compromettrait PyPI pourrait remplacer le fichier tout en gardant le même numéro
de version.
Solution : épingler avec le hash SHA256 du fichier.
requests==2.31.0 \ --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003fAvec cette configuration, pip refuse d’installer le package si son hash ne correspond pas. Même si PyPI est compromis, vous êtes protégé.
Générer automatiquement les hashes avec pip-compile :
# Installer pip-toolspip install pip-tools
# Générer requirements.txt avec hashes depuis requirements.inpip-compile --generate-hashes requirements.inContenu de requirements.in (vos dépendances directes) :
requestsflaskRésultat dans requirements.txt (généré automatiquement) :
requests==2.31.0 \ --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003fflask==3.0.0 \ --hash=sha256:...# + toutes les dépendances transitives avec leurs hashesProtection Go modules
Go a été conçu avec la sécurité de la supply chain en tête. Contrairement à npm et pip, Go n’exécute aucun code lors de l’installation des dépendances. De plus, Google maintient une base de données publique des checksums de tous les modules Go.
Pourquoi Go est plus sûr par design :
| Risque | npm/pip | Go |
|---|---|---|
| Scripts à l’installation | ⚠️ Oui (postinstall, setup.py) | ✅ Non |
| Vérification d’intégrité | ⚠️ Optionnelle | ✅ Automatique |
| Base de checksums centrale | ❌ Non | ✅ sum.golang.org |
| Proxy cache officiel | ❌ Non | ✅ proxy.golang.org |
Vérifier l’intégrité des modules
Le fichier go.sum contient les checksums de tous vos modules. La commande
go mod verify vérifie que les modules dans votre cache correspondent à ces
checksums.
go mod verifySortie attendue si tout va bien :
all modules verifiedSortie en cas de problème :
github.com/example/module v1.2.3: zip has been modifiedCela signifie que le module dans votre cache local a été altéré (attaque ou corruption). Supprimez votre cache et retéléchargez :
go clean -modcachego mod downloadRègle absolue : toujours commiter go.sum.
git add go.sumgit commit -m "chore: update go.sum"Proxy et base de checksums (configuration par défaut)
Go utilise par défaut le proxy et la base de checksums de Google. Cette configuration est recommandée et ne devrait pas être modifiée sauf cas spécifique.
# Vérifier votre configuration actuellego env GOPROXY GOSUMDBConfiguration par défaut (recommandée) :
GOPROXY=https://proxy.golang.org,directGOSUMDB=sum.golang.orgCe que ça signifie :
| Variable | Valeur | Rôle |
|---|---|---|
GOPROXY | proxy.golang.org,direct | Télécharge via le proxy Google, puis directement si indisponible |
GOSUMDB | sum.golang.org | Vérifie les checksums contre la base Google |
Avantages du proxy Google :
- Cache mondial : téléchargements plus rapides
- Immuabilité : une version ne peut jamais être modifiée après publication
- Disponibilité : fonctionne même si GitHub est down
Modules privés (exclure du proxy public)
Pour vos modules internes, vous ne voulez pas que Go essaie de les télécharger via le proxy public (ils n’y sont pas, et vous exposeriez leurs noms).
# Dans votre .bashrc ou .zshrcexport GOPRIVATE=github.com/acme-corp/*,gitlab.acme.com/*Ce que ça change :
| Module | Avec GOPRIVATE | Sans GOPRIVATE |
|---|---|---|
github.com/acme-corp/utils | Téléchargé directement (git clone) | ⚠️ Erreur 404 sur proxy.golang.org |
github.com/gin-gonic/gin | Via proxy.golang.org | Via proxy.golang.org |
Pour aller plus loin : si vous avez un proxy Go privé (Athens, Artifactory),
combinez GOPRIVATE et GOPROXY :
export GOPROXY=https://goproxy.acme.com,https://proxy.golang.org,directexport GOPRIVATE=github.com/acme-corp/*Vérifier la provenance des dépendances
Les protections précédentes (lockfile, registre privé, désactivation des scripts) sont défensives : elles réduisent la surface d’attaque. Mais elles ne répondent pas à une question fondamentale :
Ce package a-t-il été construit à partir du code source que je peux auditer ?
Un mainteneur malveillant pourrait publier un binaire différent du code source GitHub. Le code sur GitHub est propre, mais le package npm/PyPI contient une backdoor. C’est exactement ce qui s’est passé avec xz-utils en 2024 ↗.
Qu’est-ce que la provenance SLSA ?
SLSA (Supply-chain Levels for Software Artifacts, prononcé “salsa”) est un framework qui garantit la traçabilité d’un artefact jusqu’à son code source.
Une attestation SLSA prouve :
| Information | Ce que ça garantit |
|---|---|
| Source | Le code vient bien de ce dépôt GitHub |
| Builder | Construit par GitHub Actions (pas un laptop) |
| Intégrité | Le binaire correspond exactement à ce commit |
| Non-falsifiable | L’attestation est signée cryptographiquement |
Analogie : c’est comme un certificat de traçabilité alimentaire. Vous pouvez remonter du steak dans votre assiette jusqu’à la ferme d’origine.
npm : vérifier les signatures de packages
Depuis npm v8.21 (2022), npm génère automatiquement des attestations SLSA pour les packages publiés via GitHub Actions. Lors de l’installation, npm vérifie ces signatures en arrière-plan.
Vérifier que votre npm utilise le bon registre :
npm config get registryVous devez obtenir https://registry.npmjs.org/. Si ce n’est pas le cas, les
signatures ne seront pas vérifiées.
Auditer les signatures de vos dépendances installées :
npm audit signaturesExemple de sortie (tout va bien) :
audited 542 packages in 3s
542 packages have verified registry signaturesExemple de sortie (packages non signés) :
audited 542 packages in 3s
497 packages have verified registry signatures45 packages have unverified signatures or missing attestationsLes packages sans signature ne sont pas bloqués — tous les packages ne sont pas encore signés. Mais c’est un indicateur : pour vos dépendances critiques, préférez les packages signés.
Go : vérifier avec slsa-verifier
Go n’intègre pas encore nativement la vérification SLSA, mais de plus en plus de projets Go publient des attestations de provenance. Ces attestations prouvent que le binaire a été généré par GitHub Actions à partir d’un commit spécifique.
Workflow de vérification :
- Téléchargez le binaire ET son fichier d’attestation (
.intoto.jsonl) - Vérifiez que l’attestation est signée par un builder SLSA reconnu
- Vérifiez que le binaire correspond au commit attendu
Installation de slsa-verifier :
go install github.com/slsa-framework/slsa-verifier/v2/cli/slsa-verifier@latestExemple concret : vérifier le binaire cosign
Cosign est l’outil de signature d’images du projet Sigstore. Voici comment vérifier que le binaire que vous téléchargez est authentique :
# 1. Télécharger le binaire et son attestationwget https://github.com/sigstore/cosign/releases/download/v2.4.0/cosign-linux-amd64wget https://github.com/sigstore/cosign/releases/download/v2.4.0/cosign-linux-amd64.intoto.jsonl
# 2. Vérifier la provenance SLSAslsa-verifier verify-artifact cosign-linux-amd64 \ --provenance-path cosign-linux-amd64.intoto.jsonl \ --source-uri github.com/sigstore/cosign \ --source-tag v2.4.0Détail des options :
| Option | Signification |
|---|---|
verify-artifact | Mode vérification d’un fichier (vs. image container) |
--provenance-path | Chemin vers le fichier d’attestation in-toto |
--source-uri | Dépôt GitHub attendu |
--source-tag | Tag git attendu (commit exact) |
Sortie en cas de succès :
Verified signature against tlog entry index 12345678Verified build using builder https://github.com/slsa-framework/slsa-github-generatorPASSED: SLSA verification passedSortie en cas d’échec :
FAILED: SLSA verification failed: source-uri mismatchSi la vérification échoue, ne faites pas confiance au binaire. Quelqu’un a peut-être modifié le fichier ou l’attestation est forgée.
PyPI : Trusted Publishers
PyPI a introduit les Trusted Publishers en 2023. Cette fonctionnalité permet aux mainteneurs de lier leur package à un workflow GitHub Actions spécifique. Seul ce workflow peut publier de nouvelles versions — même le mainteneur ne peut pas publier depuis son laptop.
Comment ça fonctionne :
- Le mainteneur configure son projet PyPI pour accepter uniquement les
publications venant de
github.com/owner/repo/.github/workflows/release.yml - Quand le workflow s’exécute, GitHub génère un token OIDC signé
- PyPI vérifie que le token vient bien du workflow autorisé
- La publication est acceptée ou refusée
Avantages pour vous (consommateur) :
| Risque | Sans Trusted Publishers | Avec Trusted Publishers |
|---|---|---|
| Mainteneur compromis | ⚠️ Peut publier du malware | ✅ Ne peut pas (pas de token) |
| Vol de credentials PyPI | ⚠️ Publication malveillante possible | ✅ Le token est éphémère |
| Build local malveillant | ⚠️ Possible | ✅ Seul le CI peut publier |
Comment vérifier qu’un package utilise Trusted Publishers :
- Allez sur la page PyPI du package (ex:
https://pypi.org/project/requests/) - Cliquez sur “Release history” dans la barre latérale
- Les releases publiées via Trusted Publishers affichent une icône de vérification ✓ avec le texte “Published via GitHub Actions”
Exemple de packages populaires avec Trusted Publishers :
requests(depuis 2024)pydantichttpxruff
Critères pour choisir une dépendance sécurisée
Ajouter une dépendance n’est pas anodin : c’est du code que vous n’avez pas écrit qui s’exécute avec les mêmes privilèges que votre application. Avant d’ajouter une nouvelle dépendance, évaluez-la sur ces critères.
Checklist de sécurité :
| Critère | Comment vérifier | Seuil recommandé | Pourquoi c’est important |
|---|---|---|---|
| Signature SLSA | npm audit signatures / slsa-verifier | Présente | Garantit la traçabilité du build |
| Score Scorecard | scorecard --repo=... | > 5/10 | Évalue les pratiques sécurité |
| Dernière release | Page GitHub/npm/PyPI | < 6 mois | Projet actif = vulnérabilités corrigées |
| Mainteneurs actifs | Contributors sur GitHub | ≥ 2 | Bus factor : que faire si le mainteneur disparaît ? |
| Réponse aux CVE | Security advisories | < 30 jours | Capacité à réagir aux incidents |
| Branch protection | Scorecard / audit manuel | Activée | Empêche les commits malveillants directs |
Exemple : évaluer express avant de l’ajouter
# Vérifier le score Scorecardscorecard --repo=github.com/expressjs/expressSortie typique :
Starting [expressjs/express]Aggregated Score: 6.8/10
Score Check Reason---- ----- ------10/10 Code-Review All commits reviewed 7/10 Branch-Protection Branch protection on default branch 9/10 Dependency-Update Dependabot or Renovate active 8/10 Maintained 22 commits in last 90 days 0/10 SAST No SAST tools detected10/10 Signed-Releases All releases signedComment interpréter :
- Score > 5/10 : acceptable pour la plupart des projets
- Score < 3/10 : cherchez une alternative
- Vérifiez spécifiquement
Signed-ReleasesetBranch-Protection
# Exemple : évaluer express avant de l'ajouterscorecard --repo=github.com/expressjs/expressBonnes pratiques universelles
-
Toujours commiter le lockfile (
package-lock.json,go.sum, etc.) -
Utiliser
cien production (pasinstall) -
Auditer régulièrement (
npm audit,pip-audit,go mod verify) -
Registre privé pour les packages internes avec priorité explicite
-
Désactiver les scripts ou les auditer manuellement
-
Vérifier les mainteneurs avant d’adopter un nouveau package
Outils de détection
| Outil | Écosystème | Détecte |
|---|---|---|
| npq | npm | Typosquatting, scripts suspects |
| pip-audit | PyPI | Vulnérabilités connues |
| Socket | npm, PyPI | Malware, comportement suspect |
| Semgrep Supply Chain | Multi | Packages malveillants connus |
| Snyk | Multi | Vulnérabilités, license |
À retenir
- Typosquatting : une faute de frappe = code malveillant
- Dependency confusion : registre privé doit avoir priorité explicite
- Scripts d’installation : désactiver ou auditer
- Lockfile : toujours commiter, utiliser
cien production - Go : plus sécurisé par design (pas de scripts, checksums natifs)
- Provenance SLSA : vérifiez que vos dépendances critiques sont signées
- Scorecard : évaluez la maturité sécurité avant d’adopter un package