Surveillance de l'intégrité des agents
La hub exécute toutes les 10 minutes un IntegrityCheckWorker sur tous les agents actifs. Il génère aucun trafic réseau supplémentaire vers les agents — il s’agit d’une analyse purement côté serveur de ce qui est déjà arrivé.
Ce qui est contrôlé
| Type | Détection | Sévérité |
|---|---|---|
unsigned_payload | Ingestion sans en-tête X-Monsys-Signature (ou agent sans clé publique pinée) | moyen / élevé |
signature_invalid | Signature invalide vérifiée contre la clé publique pinée | critique |
clock_drift | Écart horaire moyen (captured_at − server_now) > 5 min sur les dernières 20 métriques | élevé |
cadence_anomaly | Moyenne de l’intervalle d’ingestion en dehors [7s, 23s] (attendu 15s) | moyen |
flat_metrics | Écart-type CPU < 0.001 sur 30+ échantillons dans 1 minute | élevé |
version_downgrade | Version de l’agent semver inférieure à celle précédemment observée | élevé |
Modèle ouvert / résolu
integrity_anomalies est append-only pour l’utilisateur de la hub-app (seulement INSERT et UPDATE ; DELETE/TRUNCATE est privilège révoqué). Chaque résultat se trouve dans l’un des deux états :
- ouvert —
resolved_at IS NULL - résolu — l’administrateur a cliqué sur “marquer résolu” ou la vérification passe à nouveau dans le cycle suivant
Les éléments résolus sont conservés comme traçage d’audit. Un auditeur peut voir exactement quand et par qui chaque anomalie a été constatée et fermée.
Tableau de bord
/integrity affiche 6 cartes KPI (une par type) avec le nombre d’éléments ouverts. La table ci-dessous donne la sévérité, l’agent (clicvable), une brève description, un JSON détaillé, la date de détection et un bouton pour résoudre.
Exemples
Exemple 1 : signature_invalid avec token volé
Un attaquant a volé le ms_... token, essaie d’envoyer des métriques depuis sa propre machine sans la clé de signature :
POST /api/v1/ingest Authorization: Bearer ms_<volé> Content-Type: application/json (pas d'en-tête X-Monsys-Signature)
→ 403 Forbidden→ integrity_anomalies row : type : unsigned_payload sévérité : élevée résumé : agent a une clé de signature pinée mais n'a pas transmis d'en-tête X-Monsys-Signature détecté à : 2026-05-09 22:03:40Il apparaît sur /integrity, colore rouge dans la bande KPI, et l’administrateur voit dans les 30 secondes via SWR refresh une alerte critique ouverte.
Exemple 2 : flat_metrics — agent envoie fausse telemetrie
Un attaquant avec root sur le host remplace le collecteur de métriques par un script qui envoie constamment CPU=4.20% :
SELECT STDDEV_SAMP(cpu_usage_percent), COUNT(*)FROM metricsWHERE agent_id = $1 AND time > NOW() - INTERVAL '1 heure';-- écart-type : 0,000 (sur 240 échantillons) → anomalie flat_metricsLe worker upsert après 10 minutes :
type : flat_metricssévérité : élevéerésumé : CPU écart-type = 0 sur l'heure dernière — telemetrie semble manipuléedétail : {"échantillons": 240, "écart-type": 0.0}SQL pour l’auditeur
-- Tous les anomalies ouvertes par agent, triées par sévéritéSELECT a.name, ia.type, ia.sévérité, ia.résumé, ia.détecté à FROM integrity_anomalies ia JOIN agents a ON a.id = ia.agent_id WHERE ia.resolved_at IS NULL ORDER BY CASE ia.sévérité WHEN 'critique' THEN 1 WHEN 'élevée' THEN 2 WHEN 'moyen' THEN 3 WHEN 'faible' THEN 4 ELSE 5 END, ia.détecté à DESC;
-- Nombre d'éléments résolus par type dans les 30 derniers jours ?SELECT type, count(*) FROM integrity_anomalies WHERE resolved_at > NOW() - INTERVAL '30 jours' GROUP BY type;
-- Qui a fermé quel élément ?SELECT u.email, ia.type, ia.résumé, ia.resolved_at FROM integrity_anomalies ia JOIN users u ON u.id = ia.resolved_by WHERE ia.resolved_at > NOW() - INTERVAL '30 jours' ORDER BY ia.resolved_at DESC;Limites
flat_metricsa un minimum de 30 échantillons nécessaires — les agents qui viennent d’être démarrés tombent sous la limite de détection.clock_driftutilise l’horloge du hub comme référence. Un attaquant qui compromet également le host du hub (manipulation horloges) évite cette vérification.version_downgradereconnaît uniquement les versions semver au style0.x.y. Les suffixes de build tels que0.1.0-beta1sont considérés comme équivalents à0.1.0.