
Vos développeurs lancent chaque jour des dizaines de npm install et de
pip install. Chacune de ces commandes télécharge du code tiers — souvent
des centaines de paquets transitifs — et l'exécute sur leurs postes et sur vos
runners CI, sans qu'aucun contrôle ne décide si ce code a le droit d'entrer.
Ce vecteur est massivement exploité. En 2025, le malware Shai-Hulud a infecté
plus de 500 paquets npm populaires : son script postinstall collectait
identifiants, clés SSH et portefeuilles crypto dès l'installation. Côté PyPI,
des milliers de paquets malveillants sont retirés chaque année, exploitant le
typosquatting et la
dependency confusion
pour se faire passer pour des dépendances légitimes.
Pour un RSSI, le constat est simple : la porte d'entrée des dépendances n'est pas gardée. Un firewall d'artefacts la garde — il place une décision de politique (autoriser, masquer, refuser, mettre en quarantaine) devant chaque résolution, avant que le paquet n'entre dans votre usine logicielle.
Ce guide s'adresse aux équipes sécurité et plateforme qui gèrent déjà un gestionnaire d'artefacts (Nexus, Artifactory) et veulent passer du cache au contrôle. Vous y trouverez le rôle exact d'un firewall d'artefacts, comment écrire ses règles pour npm et PyPI, et un lab d'étude reproductible avec Varnish Artifact Firewall (Varnish Orca) devant Nexus.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Distinguer cache, firewall d'artefacts, analyse SCA et admission control
- Écrire des règles
allow/hide/deny/quarantine - Monter un lab Orca + Nexus pour étudier le contrôle d'entrée
- Bloquer une version vulnérable et un nom interne usurpé
- Situer le firewall dans une chaîne supply chain complète
Pourquoi un cache ne suffit pas
Section intitulée « Pourquoi un cache ne suffit pas »Un gestionnaire d'artefacts comme Nexus ou un cache comme Varnish Orca résout deux problèmes réels : la vitesse (les dépendances sont servies localement) et la résilience (vous survivez à une panne de npmjs.org). Mais ils partagent une limite : ils servent tout ce qu'on leur demande.
Concrètement, si un développeur installe une version vulnérable de requests,
un package au nom usurpé via dependency confusion, ou une dépendance publiée
il y a deux heures par un compte compromis, le cache la télécharge, la stocke, et
la rediffuse à toute l'équipe. Le cache accélère la propagation du problème
au lieu de l'arrêter.
Le firewall d'artefacts ajoute la pièce manquante : une décision inline, prise avant que l'artefact n'entre dans le cache et dans vos builds.
Qu'est-ce qu'un firewall d'artefacts
Section intitulée « Qu'est-ce qu'un firewall d'artefacts »Un firewall d'artefacts est un proxy qui s'intercale entre vos clients (npm, pip, runners CI) et vos dépôts, et qui évalue chaque résolution contre un jeu de règles. Selon la règle qui correspond, il rend l'une de ces décisions :
| Décision | Effet | Cas d'usage |
|---|---|---|
allow | Sert l'artefact | Exception explicite, package de confiance |
hide | L'artefact existe mais n'est pas proposé à la résolution | Évincer une version sans casser un lockfile existant |
deny | Refuse, renvoie une erreur | Package interdit, version vulnérable |
quarantine | Retient temporairement (artefacts récents) | Fenêtre anti-typosquat sur publications fraîches |
L'intérêt par rapport à un scanner classique : la décision est prise avant ingestion. Une dépendance refusée n'entre jamais dans le cache ni dans le build. Là où un outil de SCA vous dit après coup « vous avez installé un composant vulnérable », le firewall l'empêche d'arriver.
Dans ce guide, j'utilise Varnish Artifact Firewall (la brique Premium de
Varnish Orca) devant Nexus. Le firewall est transparent : les clients
visent le registre virtuel d'Orca, et enable_firewall: true route ce registre
à travers le firewall en interne.
Architecture du lab d'étude
Section intitulée « Architecture du lab d'étude »Le lab enchaîne trois composants. Chaque résolution traverse le firewall avant d'atteindre le cache, puis éventuellement le registre public.
Client npm / pip -> Varnish Artifact Firewall (Orca) -> Nexus (groupes) -> npmjs.org / pypi.org décision allow/hide/deny/quarantineLe rôle de Nexus ici est de centraliser : un dépôt proxy vers le registre public, un dépôt hosted pour vos packages internes, et un groupe qui expose une URL unique. L'ordre des membres du groupe (hosted avant proxy) constitue déjà une première barrière contre la dependency confusion. Le firewall ajoute la décision par règle au-dessus de cette organisation.
Monter le lab
Section intitulée « Monter le lab »Le lab tient dans un docker-compose.yml (Nexus + Orca) et quelques fichiers de
configuration. Voici les pièces essentielles à reproduire.
-
Déclarer Nexus et Orca
docker-compose.yml services:nexus:image: sonatype/nexus3:3.77.1ports: ["8081:8081"]volumes: ["nexus-data:/nexus-data"]orca:image: varnish/orcaplatform: linux/amd64 # Orca est distribué pour linux/amd64depends_on: [nexus]ports:- "8080:80" # registres virtuels (endpoint client)- "9090:9090" # métriques firewall (Premium)volumes:- ./orca/orca.yaml:/etc/varnish-supervisor/default.yaml:ro- ./orca/rulesets:/etc/orca/rulesets:rovolumes:nexus-data: -
Créer les dépôts Nexus (proxy, hosted, groupe) pour npm et PyPI via l'API REST. Le groupe
npm-allagrègenpm-private-hostedpuisnpm-public-proxy, et c'est lui que verra le firewall. -
Configurer le firewall Orca
orca/orca.yaml virtual_registry:registries:- name: npm-nexusdefault: trueenable_firewall: trueremotes:- url: http://nexus:8081/repository/npm-all/- name: pypi-nexusenable_firewall: trueremotes:- url: http://nexus:8081/repository/pypi-all/firewall:default_action: allow # observe en POC ; deny en productiondefault_quarantine_days: 2severity_deny_threshold: 9severity_allow_threshold: 4audit_log_output: stdoutmetrics_address: ":9090"rulesets:- path: /etc/orca/rulesets/npm-rules.yaml- path: /etc/orca/rulesets/pypi-rules.yaml -
Pointer les clients sur le firewall
Orca transmet le chemin client tel quel : incluez le chemin du groupe Nexus.
.npmrc registry=http://npm-nexus.localhost:8080/repository/npm-all/pip.conf [global]index-url = http://pypi-nexus.localhost:8080/repository/pypi-all/simpletrusted-host = pypi-nexus.localhost
Écrire des règles
Section intitulée « Écrire des règles »Une règle décrit quoi reconnaître (match) et quoi décider (action).
Les règles sont regroupées dans un fichier identifié par un id, et triées par
priority. Voici un jeu de règles npm de démonstration.
id: npm-demo-rulesrules: # Blocage dur d'un package précis. - id: block-left-pad priority: 100 action: deny severity: 9.5 reason: "Package bloqué pour la démonstration." match: - type: npm name: left-pad
# Anti dependency confusion : un scope interne ne vient jamais du public. - id: block-internal-scope priority: 90 action: deny reason: "Le scope @company ne doit pas être résolu depuis le registre public." match: - type: npm name: "@company/*"
# Masquer les vieilles versions sans casser un lockfile existant. - id: hide-lodash-old priority: 50 action: hide reason: "Les versions de lodash < 4.17.21 ne sont pas proposées." match: - type: npm name: lodash version: "vers:npm/<4.17.21"Côté Python, la même grammaire bloque une version vulnérable ou un préfixe
interne. La plage de versions utilise la notation VERS (vers:pypi/<2.32.0).
id: pypi-demo-rulesrules: - id: block-old-requests priority: 100 action: deny severity: 9.0 reason: "Les versions de requests < 2.32.0 sont interdites." match: - type: pypi name: requests version: "vers:pypi/<2.32.0"Comment la sévérité interagit avec les seuils
Section intitulée « Comment la sévérité interagit avec les seuils »Une règle peut porter un score severity. Le firewall le compare aux seuils
définis dans la configuration : au-dessus de severity_deny_threshold (9), la
décision devient deny ; en dessous de severity_allow_threshold (4), elle
devient allow ; entre les deux, c'est l'action par défaut qui s'applique. Ce
mécanisme permet de brancher des règles communautaires (par exemple des jeux
de règles dérivés des données OSV) et de piloter leur effet via un seul
réglage, sans réécrire chaque règle.
Des règles dynamiques avec un dépôt git
Section intitulée « Des règles dynamiques avec un dépôt git »Charger les règles depuis des fichiers locaux (path:) impose de redémarrer
Orca pour les prendre en compte : pratique en lab, pas en production. Le mode
dynamique passe par une source git, que le firewall rafraîchit
automatiquement à intervalle régulier — sans redémarrage.
firewall: rulesets: - git: name: npm-osv-rules url: https://github.com/varnish/osv-rules.git sub_path: rulesets/npm/all.yaml ref: main # branche, tag ou SHA interval: 30m # fréquence de pull (défaut : 1h) webhook: url: http://collector.interne/reloaded # notifié après chaque rechargementCe fonctionnement débloque une vraie démarche GitOps : vos règles vivent dans
un dépôt versionné et signé, toute modification passe par une revue, et le
firewall applique la nouvelle politique au prochain cycle de interval. Le
webhook prévient vos systèmes (invalidation du cache de manifestes,
notification d'audit) à chaque rechargement réussi. Vous pouvez ainsi brancher
des jeux de règles communautaires maintenus à jour (par exemple les règles
dérivées des données OSV publiées par Varnish) sans intervention manuelle.
Étudier le firewall en pratique
Section intitulée « Étudier le firewall en pratique »Une fois la licence d'essai montée, le firewall démarre et applique les décisions. Le scénario d'étude le plus parlant compare deux installations.
-
Installer une dépendance autorisée
Fenêtre de terminal npm install is-number # autorisé : l'installation réussit -
Tenter une dépendance bloquée
Fenêtre de terminal npm install left-pad # deny : l'installation échoue -
Lire le journal d'audit
Fenêtre de terminal docker compose logs orca | grep -i denyVous y retrouvez la décision, le package et la raison (
reason) de la règle. C'est ce journal que vous routerez vers un SIEM en production. -
Modifier une règle et rejouer
Retirez
block-left-pad, redémarrez Orca, relancez l'installation : elle passe. Vous venez de démontrer que la politique pilote la décision.
Ce que le firewall ne protège pas
Section intitulée « Ce que le firewall ne protège pas »Un firewall d'artefacts ferme une porte : l'entrée non gouvernée des dépendances. Il ne traite pas le reste de la surface d'attaque, et le présenter comme une protection globale serait une erreur.
- Il ne lit pas le code d'un package autorisé : une dépendance saine aujourd'hui peut être compromise demain par une mise à jour malveillante.
- Il ne protège ni le runner CI compromis, ni l'exfiltration de secrets, ni l'image déjà construite et non signée.
- Il ne produit ni SBOM ni provenance : ce sont d'autres briques.
Surtout, la configuration registry incite à passer par le firewall mais ne
l'impose pas : un développeur ou un job peut toujours écrire npm config set registry https://registry.npmjs.org. Refermer ce contournement — sur les
runners CI comme sur les postes de dev — est un sujet à part entière,
traité juste après.
Empêcher le contournement (CI et postes de dev)
Section intitulée « Empêcher le contournement (CI et postes de dev) »Un firewall ne protège que ce qui passe par lui. Tant que les clients peuvent
joindre registry.npmjs.org ou pypi.org en direct, n'importe qui peut le
court-circuiter d'une seule commande. La règle d'or tient en une phrase : la
configuration incite, le réseau impose.
Sur les runners CI : verrouiller l'egress
Section intitulée « Sur les runners CI : verrouiller l'egress »Côté CI, le contournement se ferme proprement parce que vous maîtrisez
l'environnement. Placez les runners dans un réseau sans route sortante vers
Internet : seul le firewall d'artefacts est joignable en egress. Une tentative
npm config set registry https://registry.npmjs.org échoue alors faute de route.
Sous Kubernetes, une NetworkPolicy egress qui n'autorise que le service du
firewall produit le même effet. Des runners éphémères et standardisés
(reconstruits à chaque job) empêchent en plus toute config locale persistante.
Sur les postes de dev : le cas difficile
Section intitulée « Sur les postes de dev : le cas difficile »Le poste de développeur est plus dur à contraindre : le dev a souvent les droits
admin sur sa machine, donc aucune configuration applicative n'est
infalsifiable. Pousser un .npmrc, un pip.conf ou un GOPROXY global par
MDM (Intune, Jamf) ou gestion de configuration réduit la friction, mais reste
contournable d'un --registry. Le contrôle robuste est, là encore,
réseau :
| Solution | Effet | Robustesse |
|---|---|---|
| Couper l'egress direct (pare-feu/proxy d'entreprise) vers npmjs/pypi/Docker Hub | Le set registry public échoue : plus de route | Élevée |
DNS interne (split-horizon) : registry.npmjs.org → IP du firewall | Même une URL publique en dur atterrit sur le firewall | Élevée (avec egress fermé) |
| Poste sans route Internet directe, seul le proxy interne est routable | Tout transite par le firewall, par construction | Maximale |
| Environnement de dev distant (dev container, VDI, espace de code) | On contrôle le réseau de l'env, pas la machine physique | Élevée |
Config managée (MDM/Ansible : .npmrc, pip.conf, GOPROXY) | Baisse la friction, oriente le défaut | Faible seule |
Aucune ligne ne suffit isolément sur une machine où le dev est administrateur. La combinaison qui tient : pas de route directe vers les registres publics + DNS qui rabat les noms publics sur le firewall. La config managée s'ajoute pour le confort, pas pour la sécurité.
Rattraper et détecter
Section intitulée « Rattraper et détecter »Le réseau ferme la porte ; la détection vérifie qu'elle reste fermée. Ajoutez en
CI un contrôle qui inspecte les lockfiles : si une entrée resolved
(npm) ou une URL d'index pointe ailleurs que vers le firewall, le build échoue —
vous rattrapez ainsi un poste mal configuré avant le merge. En parallèle,
surveillez les logs du proxy réseau : toute tentative d'accès direct à
npmjs/pypi/Docker Hub devient une alerte. Cette détection des contournements
prolonge les techniques décrites dans
Attaques via les gestionnaires de paquets.
Où le placer dans votre chaîne
Section intitulée « Où le placer dans votre chaîne »Le firewall n'est pas un substitut aux autres contrôles : il occupe la position la plus en amont. Une chaîne supply chain mûre les combine.
| Étape | Brique | Rôle |
|---|---|---|
| Entrée des dépendances | Varnish Artifact Firewall | bloque/masque avant ingestion |
| Centralisation | Nexus | cache et expose les artefacts |
| Inventaire | SBOM / Syft | liste les composants |
| Vulnérabilités | Trivy / Grype | scanne après résolution |
| Signature | Cosign | signe et vérifie |
| Provenance | SLSA | atteste l'origine du build |
Le firewall réduit le bruit en aval : moins de composants douteux entrent, moins de vulnérabilités à trier ensuite. Il ne remplace pas la défense en profondeur, il la commence.
À retenir
Section intitulée « À retenir »- Un cache accélère et résiste aux pannes ; un firewall d'artefacts décide ce qui a le droit d'entrer.
- Quatre décisions : allow, hide, deny, quarantine.
- Les règles reconnaissent un package par
type/name/version(notation VERS) oupurl, et la sévérité pilote la décision via des seuils. - Le firewall traite la dependency confusion, les versions vulnérables et les publications trop récentes — pas le code malveillant d'un package autorisé.
- Sans règle d'egress réseau, la configuration registry se contourne.
- Chez Varnish, le firewall est Premium ; une licence d'essai permet de l'étudier en conditions réelles.