Technische deep-dive · 2026-05-25

Waarom de monsys-agent in Rust is geschreven — en wat dat betekent voor security

~5 MB statisch gelinkt, zero glibc-dependencies, draait op kernel 2.6+. Memory safety zonder garbage collector, scope-locked sudoers en drielaagse auto-update verificatie.

De monsys-agent is een ~5 MB statisch gelinkte Rust binary. Dat is een bewuste keuze, geen toeval. Dit artikel legt uit waarom, wat de trade-offs zijn, en wat "statisch gelinkt" concreet betekent voor deployment op een productieserver.

Het installatieprobleem van monitoring-agents

De meeste monitoring-agents hebben een dependency-probleem. Een Python-agent heeft Python nodig — de juiste versie, de juiste packages, het juiste virtual environment. Een Node-agent heeft Node nodig. Een Java-agent heeft een JVM nodig.

Op een typische Belgische KMO-server met Ubuntu 18.04 (EOL, maar nog in productie), RHEL 8, of Alpine in een Docker-container is dat een probleem. De server heeft niet de juiste runtime. Je installeert de runtime. De runtime heeft security-updates. Je hebt nu een nieuwe attack surface toegevoegd om je attack surface te monitoren.

De monsys-agent lost dit anders op: één binary, statisch gelinkt tegen musl libc, zero glibc-dependencies. De binary bevat alles wat hij nodig heeft.

Wat "statisch gelinkt" betekent

Een dynamisch gelinkte binary heeft runtime-dependencies op gedeelde libraries:

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

Als libssl.so.3 de verkeerde versie heeft, crasht de binary. Als libc.so.6 ontbreekt (Alpine gebruikt musl, niet glibc), werkt de binary niet.

Een statisch gelinkte binary bevat die libraries intern:

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

De binary werkt op elk Linux-systeem met een kernel ≥ 2.6, ongeacht de geïnstalleerde libraries. Dat dekt:

Waarom Rust voor een security-agent

Memory safety zonder garbage collector

De meest voorkomende vulnerability-klassen in C/C++ zijn buffer overflows, use-after-free, en race conditions. Rust maakt deze klassen onmogelijk op compile-time, niet via runtime-checks. Er is geen garbage collector die pauzeert, geen runtime dat extra geheugen gebruikt.

Voor een agent die op productiehosts draait met lage overhead-vereisten is dat de juiste trade-off: de veiligheid van memory-safe talen (Go, Python, Java) zonder de overhead van garbage collection of een runtime.

Kleine binary footprint

Rust compileert naar efficiënte machinecode zonder een grote runtime mee te slepen. De agent (~5 MB) past comfortabel op embedded systemen en containers met minimale disk-ruimte.

Fearless concurrency

De agent doet veel parallel: metrics pollen, inventory opbouwen, honeypot-watches onderhouden, HTTP-payloads sturen. Rust's ownership-systeem maakt data races onmogelijk op compile-time — een klasse van bugs die in productiecode notoir moeilijk te debuggen is.

De agent-structuur

De agent is opgedeeld in vier hoofdtaken die parallel draaien via Tokio (async Rust runtime):

monsys-agent
├── metrics_collector     ← CPU/RAM/disk/net elke 15s via procfs
├── inventory_collector   ← packages, services, ports, users, SSH-keys elke 60s
├── security_monitor      ├── honeypot_watcher (inotify)
│                         ├── process_dna_scanner (sha256 per top-process)
│                         └── integrity_checker (agent binary zelf)
└── payload_sender        ← HTTP/2 POST naar hub met Ed25519 signing

metrics_collector: leest /proc/stat, /proc/meminfo, /proc/diskstats, /proc/net/dev. Geen externe tools, geen shell-calls. Directe kernel-interface via procfs.

inventory_collector: roept dpkg -l, rpm -qa, systemctl list-units, ss -tlnp, getent passwd, find /root/.ssh -name "authorized_keys" aan. SSH-keys worden alleen als SHA256-fingerprint opgeslagen, nooit de sleutelinhoud.

honeypot_watcher: registreert inotify-watches op alle canary-paden. Op Linux gebruik je inotify_add_watch via de kernel-syscall — geen polling, geen CPU-gebruik bij afwezigheid van events.

payload_sender: serialiseert events naar canonical JSON, signt met Ed25519 private key, stuurt via HTTP/2 POST naar api.monsys.ai. Bij netwerk-failure: exponential backoff, lokale queue (in-memory, max 1000 events), geen disk-writes (vermijdt disk-full scenarios).

Scope-locked sudoers: minimale privileges

De installer schrijft de volgende sudoers-regel:

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

Dat is alles. De agent draait als een dedicated monsys-user, niet als root. Alleen twee specifieke wrappers hebben sudo-toegang:

Elke andere actie die root vereist, is niet mogelijk vanuit de agent. Een aanvaller die de agent compromitteert, heeft geen sudo-escalatie naar root via de agent.

Agent integrity monitoring: de agent bewaakt zichzelf

Bij de eerste start berekent de agent zijn eigen SHA256 en stuurt die naar de hub:

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

De hub pinnt deze hash. Bij elke heartbeat controleert de agent opnieuw zijn eigen hash. Als een aanvaller de agent-binary vervangt, detecteert de nieuwe binary dat zijn hash niet overeenkomt met wat de hub verwacht — en de hub krijgt een integrity-alert.

Ja, dit heeft een bootstrapping-probleem: als een aanvaller de binary vervangt door een versie die valse integrity-hashes stuurt, is detectie moeilijker. Dat is een bewust geaccepteerde beperking — de signing chain (Ed25519) maakt het genereren van valide gesigneerde payloads moeilijk zonder de private key, maar niet onmogelijk als de hele agent gecompromitteerd is.

Auto-update: hoe dat veilig werkt

Auto-updates zijn opt-in. Wanneer ingeschakeld:

  1. Hub controleert elk 6 uur de release-manifest op releases.monsys.ai
  2. Als er een nieuwe versie is, downloadt de hub (niet de agent) de binary
  3. Hub verifieert SHA256 en de Release-signature (Ed25519 gesigneerd door monsys)
  4. Hub stuurt een self_update EAT naar de agent
  5. Agent verifieert de EAT-signature (Ed25519 van de hub)
  6. monsys-self-update wrapper downloadt de binary, verifieert SHA256 opnieuw, doet een atomaire swap (mv), herstart de systemd-service

Er zijn drie lagen van verificatie: de hub verifieert de release-signature van monsys, de agent verifieert de EAT-signature van de hub, en de wrapper verifieert de SHA256 van de binary. Een supply-chain-aanval op de release-server moet alle drie layers omzeilen.

Na de update wordt de nieuwe binary-hash gesigneerd en gestuurd naar de hub. Process DNA registreert de nieuwe hash als legitiem (manifest-aware rebaseline), zodat geen false-positive alert vuurt.

Windows: dezelfde aanpak, andere syscalls

De Windows-versie (monsys-agent.exe) gebruikt dezelfde Rust-codebase met platform-specifieke implementaties:

De binary draait als Windows Service, niet als interactieve applicatie. De noodconsole op Windows gebruikt PowerShell via ConPTY in een JEA-endpoint (Just Enough Administration) met ~60 whitelisted IR-cmdlets — geen Invoke-Expression, geen Remove-Item, geen privilege escalation.

Waarom geen eBPF

eBPF is populair voor security-monitoring: je kunt kernel-events observeren zonder een kernel-module te schrijven. Maar eBPF vereist kernel ≥ 4.9 voor basis-functionaliteit en ≥ 5.8 voor de volledige feature set.

Op Ubuntu 18.04 (kernel 4.15) of RHEL 8 (kernel 4.18) is de eBPF-feature set beperkt. Op Alpine in Docker is eBPF afhankelijk van de host-kernel.

De monsys-agent kiest voor procfs en inotify: stabiele, universeel beschikbare kernel-interfaces die werken op kernel 2.6+. Minder powerful dan eBPF, maar universeel inzetbaar. Voor de use cases die monsys dekt (metrics, inventory, honeypots, process DNA) is procfs + inotify voldoende.

Samenvatting: de design-principes van de agent

PrincipeImplementatie
Geen runtime-dependenciesStatisch gelinkt, musl libc
Minimale privilegesDedicated user, scope-locked sudoers
Memory safetyRust (compile-time guarantees)
Lage overhead~5 MB binary, < 1% CPU, < 50 MB RAM
Universeel inzetbaarkernel ≥ 2.6, alle Linux-distros + Windows x64
Zelf-bewakingEigen binary-hash gesigneerd en gecontroleerd
Veilige updatesDrielaagse verificatie, atomaire swap
Audit-trailEd25519-gesigneerde payloads, EAT-gelogde acties

Een monitoring-agent die zelf een security-risico is, werkt averechts. De ontwerp-keuzes van de monsys-agent zijn gericht op één doel: zo weinig mogelijk attack surface toevoegen op de hosts die hij bewaakt.


De agent-architectuur is gedocumenteerd in docs.monsys.ai/nl/get-started/architecture. Installeer in 30 seconden: monsys.ai/nl/signup.

Terug naar blog