Deep-dive technique · 2026-05-25

Pourquoi l'agent monsys est écrit en Rust — et ce que cela signifie pour la sécurité

~5 Mo statiquement lié, zéro dépendance glibc, tourne sur kernel 2.6+. Memory safety sans garbage collector, sudoers scope-locked et vérification auto-update à trois couches.

L'agent monsys est une binaire Rust statiquement liée d'environ 5 Mo. C'est un choix délibéré, pas un hasard. Cet article explique pourquoi, quels sont les compromis, et ce que « statiquement lié » signifie concrètement pour le déploiement sur un serveur de production.

Le problème d'installation des agents de monitoring

La plupart des agents de monitoring ont un problème de dépendance. Un agent Python a besoin de Python — la bonne version, les bons packages, le bon virtual environment. Un agent Node a besoin de Node. Un agent Java a besoin d'une JVM.

Sur un serveur PME belge typique avec Ubuntu 18.04 (EOL, mais encore en production), RHEL 8, ou Alpine dans un container Docker, c'est un problème. Le serveur n'a pas le bon runtime. Vous installez le runtime. Le runtime a des security updates. Vous avez maintenant ajouté une nouvelle surface d'attaque pour surveiller votre surface d'attaque.

L'agent monsys résout cela autrement : une binaire, statiquement liée contre musl libc, zéro dépendance glibc. La binaire contient tout ce dont elle a besoin.

Ce que « statiquement lié » signifie

Une binaire liée dynamiquement a des dépendances runtime sur des bibliothèques partagées :

ldd /usr/bin/curl
    libssl.so.3 => /lib/x86_64-linux-gnu/libssl.so.3
    libcrypto.so.3 => /lib/x86_64-linux-gnu/libcrypto.so.3
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6

Si libssl.so.3 a la mauvaise version, la binaire crash. Si libc.so.6 manque (Alpine utilise musl, pas glibc), la binaire ne fonctionne pas.

Une binaire statiquement liée contient ces bibliothèques en interne :

ldd /usr/local/bin/monsys-agent
    not a dynamic executable

La binaire fonctionne sur tout système Linux avec un kernel ≥ 2.6, quelles que soient les bibliothèques installées. Cela couvre :

Pourquoi Rust pour un agent de sécurité

Memory safety sans garbage collector

Les classes de vulnérabilités les plus fréquentes en C/C++ sont les buffer overflows, use-after-free, et race conditions. Rust rend ces classes impossibles à compile-time, pas via des checks à runtime. Il n'y a pas de garbage collector qui met en pause, pas de runtime qui consomme de la mémoire supplémentaire.

Pour un agent qui tourne sur des hôtes de production avec des exigences d'overhead faibles, c'est le bon compromis : la sûreté des langages memory-safe (Go, Python, Java) sans l'overhead de la garbage collection ou d'un runtime.

Petite empreinte binaire

Rust compile vers du code machine efficace sans trimballer un gros runtime. L'agent (~5 Mo) tient confortablement sur les systèmes embarqués et les containers à faible espace disque.

Fearless concurrency

L'agent fait beaucoup en parallèle : poller les metrics, construire l'inventaire, maintenir les watches honeypot, envoyer des payloads HTTP. Le système d'ownership de Rust rend les data races impossibles à compile-time — une classe de bugs notoirement difficiles à débuguer en production.

La structure de l'agent

L'agent est divisé en quatre tâches principales qui tournent en parallèle via Tokio (runtime async Rust) :

monsys-agent
├── metrics_collector     ← CPU/RAM/disk/net toutes les 15s via procfs
├── inventory_collector   ← packages, services, ports, utilisateurs, clés SSH toutes les 60s
├── security_monitor      ├── honeypot_watcher (inotify)
│                         ├── process_dna_scanner (sha256 par top-process)
│                         └── integrity_checker (la binaire agent elle-même)
└── payload_sender        ← HTTP/2 POST vers le hub avec signature Ed25519

metrics_collector : lit /proc/stat, /proc/meminfo, /proc/diskstats, /proc/net/dev. Pas d'outils externes, pas d'appels shell. Interface kernel directe via procfs.

inventory_collector : invoque dpkg -l, rpm -qa, systemctl list-units, ss -tlnp, getent passwd, find /root/.ssh -name "authorized_keys". Les clés SSH ne sont stockées qu'en empreinte SHA256, jamais le contenu de la clé.

honeypot_watcher : enregistre des inotify watches sur chaque chemin canary. Sur Linux on utilise inotify_add_watch via le syscall noyau — pas de polling, pas d'utilisation CPU en l'absence d'événements.

payload_sender : sérialise les événements en JSON canonique, signe avec la clé privée Ed25519, envoie via HTTP/2 POST vers api.monsys.ai. En cas d'échec réseau : exponential backoff, queue locale (en mémoire, max 1000 événements), pas d'écritures disque (évite les scénarios disque plein).

Sudoers scope-locked : privilèges minimaux

L'installer écrit la règle sudoers suivante :

monsys ALL=(ALL) NOPASSWD: /usr/local/sbin/monsys-emergency-action
monsys ALL=(ALL) NOPASSWD: /usr/local/sbin/monsys-self-update

C'est tout. L'agent tourne comme un utilisateur dédié monsys, pas comme root. Seulement deux wrappers spécifiques ont accès sudo :

Toute autre action qui exige root n'est pas possible depuis l'agent. Un attaquant qui compromet l'agent n'a aucune escalade sudo vers root via l'agent.

Agent integrity monitoring : l'agent se surveille lui-même

Au premier démarrage, l'agent calcule son propre SHA256 et l'envoie au hub :

{
  "agent_binary_hash": "sha256:a3f2c1...",
  "agent_version": "0.14.2",
  "observed_at": "2026-05-25T09:00:00Z"
}

Le hub épingle ce hash. À chaque heartbeat, l'agent recalcule son propre hash. Si un attaquant remplace la binaire agent, la nouvelle binaire détecte que son hash ne correspond pas à ce que le hub attend — et le hub reçoit une alerte intégrité.

Oui, cela a un problème de bootstrapping : si un attaquant remplace la binaire par une version qui envoie de faux hashes d'intégrité, la détection est plus difficile. C'est une limite délibérément acceptée — la chaîne de signature (Ed25519) rend la production de payloads signés valides difficile sans la clé privée, mais pas impossible si tout l'agent est compromis.

Auto-update : comment cela fonctionne en sécurité

Les auto-updates sont opt-in. Quand activés :

  1. Le hub vérifie le manifest de release sur releases.monsys.ai toutes les 6 heures
  2. S'il y a une nouvelle version, le hub (pas l'agent) télécharge la binaire
  3. Le hub vérifie SHA256 et la signature Release (Ed25519, signée par monsys)
  4. Le hub envoie un EAT self_update à l'agent
  5. L'agent vérifie la signature de l'EAT (Ed25519 du hub)
  6. Le wrapper monsys-self-update télécharge la binaire, re-vérifie SHA256, fait un swap atomique (mv), redémarre le service systemd

Trois couches de vérification : le hub vérifie la signature release de monsys, l'agent vérifie la signature EAT du hub, et le wrapper vérifie le SHA256 de la binaire. Une attaque supply-chain sur le serveur de release doit contourner les trois couches.

Après la mise à jour, le nouveau hash binaire est signé et envoyé au hub. Process DNA enregistre le nouveau hash comme légitime (rebaseline manifest-aware), donc aucune alerte faux-positif ne se déclenche.

Windows : même approche, autres syscalls

La version Windows (monsys-agent.exe) utilise le même codebase Rust avec des implémentations spécifiques à la plateforme :

La binaire tourne comme Windows Service, pas comme application interactive. La console d'urgence sur Windows utilise PowerShell via ConPTY dans un endpoint JEA (Just Enough Administration) avec ~60 cmdlets IR whitelistés — pas de Invoke-Expression, pas de Remove-Item, pas d'escalade de privilèges.

Pourquoi pas d'eBPF

eBPF est populaire pour la surveillance sécurité : on peut observer des événements noyau sans écrire un module noyau. Mais eBPF exige un kernel ≥ 4.9 pour les fonctionnalités de base et ≥ 5.8 pour le feature set complet.

Sur Ubuntu 18.04 (kernel 4.15) ou RHEL 8 (kernel 4.18) le feature set eBPF est limité. Sur Alpine dans Docker, eBPF dépend du kernel hôte.

L'agent monsys choisit procfs et inotify : interfaces noyau stables, universellement disponibles, qui fonctionnent sur kernel 2.6+. Moins puissant qu'eBPF, mais universellement déployable. Pour les use cases que monsys couvre (metrics, inventaire, honeypots, process DNA), procfs + inotify suffit.

Récapitulatif : les principes de design de l'agent

PrincipeImplémentation
Pas de dépendances runtimeStatiquement lié, musl libc
Privilèges minimauxUtilisateur dédié, sudoers scope-locked
Memory safetyRust (garanties compile-time)
Faible overhead~5 Mo binaire, < 1 % CPU, < 50 Mo RAM
Universellement déployablekernel ≥ 2.6, toutes les distros Linux + Windows x64
Auto-surveillanceHash binaire propre signé et vérifié
Updates sûresVérification à trois couches, swap atomique
Audit trailPayloads signés Ed25519, actions EAT-loggées

Un agent de monitoring qui est lui-même un risque sécurité va à l'encontre du but. Les choix de design de l'agent monsys visent un objectif : ajouter aussi peu de surface d'attaque que possible aux hôtes qu'il surveille.


L'architecture de l'agent est documentée dans docs.monsys.ai/fr/get-started/architecture. Installation en 30 secondes : monsys.ai/fr/signup.

Retour au blog