Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English
Read in English

Sécuriser les dépendances npm, PyPI et Go

Par mis à jour le

Améliorer cette page

Une dépendance est du code tiers qui s'exécute avec les droits de votre application, et parfois dès son installation. Cette leçon montre comment fermer les trois portes les plus utilisées par les attaquants, le typosquatting, la dependency confusion et les scripts d'installation, puis comment figer et vérifier ce que vous installez sur npm, PyPI et les modules Go. Elle s'adresse à un développeur ou un DevOps qui gère des dépendances au quotidien. Les commandes ont été jouées le 1er octobre 2026 avec npm 10.9.9, pip-audit 2.9.0, Go 1.25 et cosign 3.1.3.

  • Reconnaître le typosquatting, la dependency confusion et les scripts d'installation malveillants
  • Désactiver les scripts npm et inspecter ceux d'un paquet avant de les autoriser
  • Figer les dépendances par lockfile, npm ci et hash pip
  • Protéger les paquets internes avec une portée npm et un index unique
  • Vérifier la provenance d'un paquet npm, d'un paquet PyPI ou d'un binaire signé
  • Rattacher ces mesures aux exigences SOCLE-FRN-OSS-3 et SOCLE-INT-OSS-2 du référentiel SOCLE

Trois techniques reviennent dans la plupart des compromissions de dépendances. Elles ne visent pas votre code mais votre gestionnaire de paquets, et s'appuient sur ses comportements par défaut : installer ce qu'on lui demande, choisir une version, exécuter les scripts qu'un paquet déclare.

Le typosquatting exploite les fautes de frappe. L'attaquant publie un paquet dont le nom ressemble à celui d'un paquet populaire, et attend qu'un développeur se trompe en tapant la commande.

Paquet légitimeTyposquatDifférence
lodashlodahsInversion de lettres
requestsrequestSingulier et pluriel
tensorflowtensorfowLettre manquante
@angular/coreangular-coreFormat différent

Le scénario est banal : on veut requests, le client HTTP Python très répandu, et l'on tape une lettre de moins. Le paquet malveillant s'installe sans erreur, embarque souvent le vrai code du paquet imité pour ne pas éveiller les soupçons, et exécute à l'installation un script qui exfiltre les identifiants présents sur le poste. PyPI et npm retirent régulièrement de tels paquets, mais certains restent en ligne plusieurs jours avant d'être détectés.

Fenêtre de terminal
# Une lettre de moins, un autre paquet
pip install request # ❌ nom proche, autre auteur
pip install requests # le paquet attendu

La dependency confusion vise les paquets internes d'une entreprise. L'attaquant découvre leur nom, par une fuite de package.json, un message d'erreur ou du code publié, puis publie sur le registre public un paquet du même nom, dans une version très élevée comme 99.0.0. Si le gestionnaire de paquets peut trouver ce nom sur le registre public, il risque de préférer la version la plus haute. Le schéma suivant montre ce mécanisme avec pip, et la parade qui le neutralise.

Dependency confusion avec pip : l'attaquant publie acme-utils 99.0.0 sur PyPI public, pip configuré avec extra-index-url consulte à la fois PyPI et l'index privé qui porte acme-utils 1.2.0, retient la version la plus haute et installe 99.0.0 sur la machine de build ; avec un index unique, le proxy privé, c'est la version interne 1.2.0 qui est installée.version la plus hauteparadeAttaquant : publieacme-utils 99.0.0PyPI publicIndex privé :acme-utils 1.2.0pip avecextra-index-url99.0.0 installéesur la machine de buildParade : index unique,le proxy privé1.2.0 interneinstallée
Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

Le risque dépend de l'écosystème, et c'est le point que les guides simplifient le plus souvent :

SituationRisque
pip avec --extra-index-urlÉlevé : pip consulte tous les index et retient la version la plus haute
npm, nom interne sans portée (acme-utils)Élevé : le registre par défaut est le registre public
Registre proxy qui fusionne public et privé (dépôt virtuel)Élevé : le proxy peut servir la version publique la plus haute
npm, nom à portée (@acme/utils) associée à un registre privéFaible : une portée se résout sur un seul registre
npm, portée non réservée sur npmjs.comÉlevé : l'attaquant peut créer l'organisation @acme et y publier

En 2021, le chercheur Alex Birsan a démontré l'attaque contre Microsoft, Apple et PayPal en publiant des paquets aux noms de leurs dépendances internes : son code s'est exécuté sur leurs machines de build, et il a reçu plus de 130 000 dollars de primes. Les parades, détaillées par écosystème plus bas, tiennent en deux mesures : portée npm réservée et associée au registre privé, index pip unique.

Les gestionnaires de paquets savent exécuter du code au moment de l'installation. C'est utile pour compiler un module natif, et c'est aussi une porte d'entrée classique des logiciels malveillants : le code s'exécute avant même que vous n'ayez importé le paquet.

GestionnaireMécanismeExécute du code à l'installation ?
npmpreinstall, install, postinstallOui, par défaut
pipconstruction d'une distribution source (setup.py, backend de build)Oui, si aucun wheel n'est disponible
GoaucunNon

npm est une cible de choix, par sa taille et par ses scripts d'installation. Les protections ci-dessous vont de la plus simple à la plus robuste, et se cumulent.

Un paquet peut déclarer des scripts que npm lance pendant npm install. Mesuré : esbuild 0.24.2 déclare un postinstall (node install.js), et npm install l'exécute. Avec ignore-scripts=true, le même npm install n'exécute plus aucun script et installe les 72 paquets du projet de test.

.npmrc
# Aucun script de paquet n'est exécuté à l'installation
ignore-scripts=true
Fenêtre de terminal
# Ou ponctuellement, sans fichier de configuration
npm install --ignore-scripts

Avant d'autoriser un paquet qui a réellement besoin de ses scripts, comme un module natif, lisez ce qu'ils font. npm view affiche les scripts déclarés sans rien installer. N'utilisez pas npm run-script --dry-run pour cela : il ne liste que les scripts de votre projet et n'apprend rien sur un paquet tiers.

Fenêtre de terminal
npm view esbuild@0.24.2 scripts
# { postinstall: 'node install.js' }

npq s'intercale avant npm install et confronte le paquet demandé à plusieurs signaux : paquet connu comme malveillant, présence de scripts, âge, popularité, mainteneurs. Mesuré sur le typosquat lodahs, il bloque l'installation et cite la base qui le signale :

Fenêtre de terminal
npx npq@3 install lodahs --dry-run
Packages with issues found:
--- Package: lodahs@latest ---
ERROR: Supply Chain Security - Malicious package found: https://snyk.io/vuln/npm:lodahs
Summary:
- Total packages: 1
- Total errors: 1
- Total warnings: 0

Par défaut, npm enregistre des plages de versions (^4.17.0 signifie « 4.17.0 ou plus récent, sans changer de version majeure »). Si une version malveillante 4.18.0 est publiée, le prochain npm install sans lockfile la prend. Mesuré : l'option save-exact vaut false par défaut ; passez-la à true pour que npm enregistre la version exacte.

Fenêtre de terminal
npm config set save-exact true
NotationSignificationRisque
"4.17.21"Exactement cette versionMaîtrisé
"^4.17.0"De 4.17.0 à 4.x.xMises à jour mineures automatiques
"~4.17.0"De 4.17.0 à 4.17.xCorrectifs automatiques
"*"N'importe quelle version❌ À proscrire

Le lockfile package-lock.json fige la version exacte et l'empreinte (integrity) de chaque dépendance, directe ou transitive. Il se commite toujours. En CI, npm ci l'applique strictement là où npm install peut le réécrire. Mesuré : sans lockfile, npm ci refuse de s'exécuter (EUSAGE) ; avec un lockfile qui ne correspond plus au package.json, il refuse aussi, au lieu de l'adapter en silence.

Fenêtre de terminal
npm ci
# npm error code EUSAGE
# npm error `npm ci` can only install packages when your package.json and
# package-lock.json or npm-shrinkwrap.json are in sync.
CommandeComportementUsage
npm installPeut mettre à jour les dépendances et le lockfilePoste de développement
npm ciInstalle exactement le lockfile, échoue s'il divergeCI/CD, production

Contre la dependency confusion, deux mesures se complètent. Donnez à vos paquets internes une portée (@acme/…) et associez-la à votre registre privé dans le .npmrc : npm ne cherchera jamais un paquet @acme/* ailleurs. Puis réservez l'organisation @acme sur npmjs.com, même vide, pour qu'aucun tiers ne puisse y publier.

.npmrc
# Tous les paquets @acme/* viennent UNIQUEMENT du registre privé
@acme:registry=https://npm.acme-corp.example/
# Le reste vient du registre public
registry=https://registry.npmjs.org/

Si votre registre privé est un proxy qui agrège le registre public, vérifiez qu'il ne sert jamais la version publique d'un nom interne : c'est par ce type de dépôt virtuel que la confusion revient.

PyPI exige désormais l'authentification à deux facteurs de tous ses mainteneurs et propose la publication sans jeton par Trusted Publishers. Il reste exposé au typosquatting, et pip exécute du code quand il doit construire un paquet depuis ses sources.

pip-audit résout les dépendances d'un requirements.txt et les confronte aux bases de vulnérabilités (OSV et la base d'avis de PyPI). Mesuré sur un fichier qui ne contient que requests==2.25.1 : il trouve 13 vulnérabilités dans 3 paquets, requests et ses dépendances idna et urllib3, et sort en code 1, ce qui suffit à faire échouer un job de CI.

Fenêtre de terminal
pip install pip-audit==2.9.0
pip-audit -r requirements.txt
Found 13 known vulnerabilities in 3 packages
Name Version ID Fix Versions
-------- ------- ------------------- ------------
requests 2.25.1 PYSEC-2023-74 2.31.0
requests 2.25.1 PYSEC-2026-1873 2.32.0

pip exécute du code quand il construit un paquet depuis une distribution source (.tar.gz). Un wheel (.whl) est déjà construit : son installation n'exécute pas de code. L'option --only-binary :all: interdit les distributions source.

Fenêtre de terminal
pip install --only-binary :all: requests

La contrepartie est mesurable : pyyaml==5.3.1 n'a pas de wheel pour Python 3.12, et pip répond No matching distribution found. Ce refus est voulu ; il signale un paquet à auditer ou à remplacer par une version qui publie des wheels.

Type de paquetExtensionExécute du code à l'installation ?
Distribution source.tar.gzOui (construction)
Wheel.whlNon

Avec extra-index-url, pip consulte tous les index et retient la version la plus haute : un 99.0.0 publié sur PyPI l'emporte sur votre 1.2.0 interne. La parade est de ne déclarer qu'un seul index, votre registre privé, qui sert de proxy vers PyPI pour les paquets publics et réserve les noms internes.

pip.conf
[global]
# Un seul index : le proxy privé, qui sert aussi les paquets publics
index-url = https://pypi.acme-corp.example/simple/

Le fichier se place dans ~/.config/pip/pip.conf sous Linux, ~/Library/Application Support/pip/pip.conf sous macOS et %APPDATA%\pip\pip.ini sous Windows.

Épingler une version ne protège pas contre un fichier remplacé sous le même numéro. Le hash SHA-256 du fichier, ajouté à la ligne, le permet : pip refuse d'installer un fichier dont l'empreinte diffère. Mesuré : le hash du wheel requests-2.31.0-py3-none-any.whl est bien celui ci-dessous, et un hash faux fait échouer l'installation avec Expected sha256 … Got ….

requirements.txt
requests==2.31.0 \
--hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f

pip-compile, de pip-tools, génère ces lignes pour toutes les dépendances, directes et transitives, à partir d'un requirements.in qui ne liste que vos dépendances directes.

Fenêtre de terminal
pip install pip-tools
pip-compile --generate-hashes requirements.in

Go a été conçu avec la chaîne d'approvisionnement en tête. Il n'exécute aucun code à l'installation d'un module, et vérifie par défaut chaque module contre une base de checksums publique. Mesuré sur Go 1.25 : GOPROXY vaut https://proxy.golang.org,direct et GOSUMDB vaut sum.golang.org.

RisquenpmpipGo
Scripts à l'installationOui, par défautOui, pour une distribution sourceNon
Empreinte des dépendancesDans le lockfileOptionnelle (--hash)go.sum, obligatoire
Base de checksums publiqueNonNonsum.golang.org

Le fichier go.sum contient l'empreinte de chaque module, et go mod verify vérifie que le cache local leur correspond. Mesuré : sur un cache intact, la commande répond all modules verified ; après l'ajout d'une ligne dans un fichier du module github.com/google/uuid, elle répond dir has been modified et sort en code 1.

Fenêtre de terminal
go mod verify
# all modules verified
# Après altération du cache
# github.com/google/uuid v1.6.0: dir has been modified (/go/pkg/mod/github.com/google/uuid@v1.6.0)

Un cache altéré se purge et se retélécharge, et go.sum se commite toujours :

Fenêtre de terminal
go clean -modcache
go mod download

Par défaut, Go demande chaque module au proxy public et vérifie son empreinte contre sum.golang.org : un module interne y est introuvable, et sa requête révèle son nom. La variable GOPRIVATE liste les préfixes de modules privés, que Go ne demande ni au proxy ni à la base de checksums (documentation de go help private).

Fenêtre de terminal
export GOPRIVATE=github.com/acme-corp/*,gitlab.acme.example/*
# Avec un proxy privé (Athens, Artifactory), combinez les deux
export GOPROXY=https://goproxy.acme.example,https://proxy.golang.org,direct

Les protections précédentes réduisent la surface d'attaque, mais ne répondent pas à une question : ce paquet a-t-il été construit à partir du code source que l'on peut auditer ? Dans la porte dérobée de xz-utils, en 2024, le script qui activait la charge malveillante ne figurait que dans les archives de publication, pas dans le dépôt Git : relire le code du dépôt ne suffisait pas à la voir.

Une provenance répond à cette question. C'est une attestation signée qui relie l'artefact à son dépôt, à son commit et à la plateforme qui l'a construit. Le cadre SLSA (Supply-chain Levels for Software Artifacts) en définit les niveaux d'exigence, et le format in-toto en décrit les attestations.

npm distingue deux preuves. Toutes les versions du registre public portent une signature de registre, qui prouve que le paquet n'a pas été modifié depuis sa publication. La provenance, elle, n'existe que si le mainteneur a publié avec npm publish --provenance depuis une CI compatible. npm install ne vérifie ni l'une ni l'autre : c'est npm audit signatures qui le fait.

Fenêtre de terminal
npm audit signatures
audited 72 packages in 0s
72 packages have verified registry signatures
1 package has a verified attestation

Mesuré sur le projet de test (express, lodash, esbuild) : les 72 paquets ont une signature de registre valide, mais un seul porte une attestation de provenance. La provenance reste minoritaire : servez-vous-en comme critère de choix pour vos dépendances critiques, pas comme condition bloquante générale.

Les Trusted Publishers, introduits par PyPI en 2023, permettent à un projet de publier depuis un workflow de CI identifié, par jeton OIDC éphémère, sans jeton d'API stocké. Attention à ce qu'ils garantissent : ils n'empêchent pas un mainteneur de publier par ailleurs avec un jeton d'API tant que celui-ci existe. Depuis fin 2024, PyPI expose aussi des attestations pour les fichiers publiés de cette façon, interrogeables par son API.

Fenêtre de terminal
curl -s -H 'Accept: application/vnd.pypi.integrity.v1+json' \
https://pypi.org/integrity/pydantic/2.11.7/pydantic-2.11.7-py3-none-any.whl/provenance

Mesuré : pydantic 2.11.7 et sampleproject 4.0.0 renvoient une attestation qui nomme le dépôt et le workflow de publication (pydantic/pydantic, ci.yml). requests 2.32.4 renvoie 404 : ce paquet très populaire ne publie pas d'attestation. Ne supposez donc jamais qu'un paquet connu est publié par un Trusted Publisher : vérifiez-le.

Un binaire téléchargé depuis une page de publication se vérifie avec sa signature. Le format le plus courant est le bundle Sigstore (.sigstore.json), vérifié avec cosign. Mesuré sur cosign 3.1.3, qui signe ses propres binaires :

Fenêtre de terminal
# 1. Télécharger le binaire et son bundle
gh release download v3.1.3 --repo sigstore/cosign \
--pattern 'cosign-linux-amd64' --pattern 'cosign-linux-amd64.sigstore.json'
# 2. Vérifier la signature ET l'identité du signataire
cosign verify-blob cosign-linux-amd64 \
--bundle cosign-linux-amd64.sigstore.json \
--certificate-identity keyless@projectsigstore.iam.gserviceaccount.com \
--certificate-oidc-issuer https://accounts.google.com
# Verified OK

L'option --certificate-identity est décisive : sans elle, n'importe quelle signature Sigstore valide passerait, et vous auriez vérifié la cryptographie sans vérifier l'origine. Le certificat du bundle mesuré porte bien l'identité keyless@projectsigstore.iam.gserviceaccount.com. En cas d'échec, n'exécutez pas le binaire.

Ajouter une dépendance revient à faire confiance à du code et à des personnes que vous ne connaissez pas. Quelques critères se vérifient en quelques minutes, avant l'ajout, et évitent les dépendances abandonnées ou mal tenues.

CritèreComment vérifierRepère
Score OpenSSF ScorecardAPI publique ou scorecard --repo=…Au-dessus de 5 sur 10
Dernière publicationPage du paquetMoins de 6 mois
Mainteneurs actifsContributeurs du dépôtAu moins 2
Provenancenpm audit signatures, API d'intégrité PyPIPrésente si possible
Réactivité aux faillesAvis de sécurité du dépôtCorrectifs en semaines, pas en mois

L'API publique de Scorecard évite d'installer l'outil. Mesuré le 1er octobre 2026 pour expressjs/express : score 8.9 sur 10 (Scorecard v5.5.0), avec Code-Review, Maintained et SAST à 10. Deux contrôles valent -1, c'est-à-dire non évaluables, pas nuls : Signed-Releases faute de publication GitHub, et Branch-Protection faute de droits suffisants pour la lire. Un score se lit donc contrôle par contrôle, pas seulement globalement.

Fenêtre de terminal
curl -s https://api.securityscorecards.dev/projects/github.com/expressjs/express \
| jq '{score, date, checks: [.checks[] | {name, score}]}'

Le guide OpenSSF Scorecard détaille l'outil et son intégration en CI.

Ces six règles résument la leçon et valent pour les trois écosystèmes. Elles se mettent en place dans cet ordre, de la plus rapide à la plus structurante.

  1. Commiter le lockfile : package-lock.json, go.sum, requirements.txt avec hashes.

  2. Installer strictement en CI : npm ci, pip install --require-hashes, go mod verify.

  3. Auditer régulièrement : npm audit, pip-audit, et un scanner SCA en CI.

  4. Isoler les paquets internes : portée npm réservée, index pip unique, GOPRIVATE.

  5. Désactiver les scripts d'installation, et autoriser au cas par cas après lecture.

  6. Vérifier la provenance des dépendances critiques avant de les adopter.

Les outils ci-dessous complètent les gestionnaires de paquets. Les deux premiers ont été mesurés dans cette leçon ; les suivants élargissent la couverture à la base OSV ou à la détection de comportements suspects dans le code des paquets.

OutilÉcosystèmeDétecte
npqnpmPaquets malveillants connus, scripts, signaux faibles
pip-auditPyPIVulnérabilités connues
OSV-ScannerMultiVulnérabilités et paquets malveillants référencés dans OSV
Socketnpm, PyPIComportements suspects dans le code des paquets
SnykMultiVulnérabilités et licences
  1. Le typosquatting exploite une faute de frappe ; npq bloque les paquets malveillants connus.
  2. La dependency confusion frappe pip avec extra-index-url et npm sans portée réservée.
  3. ignore-scripts=true coupe les scripts npm ; npm view <paquet> scripts les montre avant.
  4. Le lockfile se commite, et npm ci refuse un lockfile absent ou désynchronisé.
  5. Un hash pip refuse un fichier remplacé ; go mod verify détecte un cache altéré.
  6. npm install ne vérifie rien : npm audit signatures contrôle signatures et provenance, encore rare.
  7. Une signature se vérifie avec l'identité du signataire, jamais sans.
  8. Preuves attendues par SOCLE-FRN-OSS-3 et INT-OSS-2 : des versions verrouillées et une liste de registres autorisés.

Vérifiez que l'essentiel de cette leçon est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ces documentations de référence approfondissent chaque écosystème.

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn