
Moniteur automatisé de sécurité des chaînes d'approvisionnement qui interroge les registres PyPI et npm, compare les nouvelles versions avec leurs prédécesseurs, et utilise l'analyse LLM pour détecter les modifications malveillantes de code avec alertes Slack.
Surveillance automatisée des principaux paquets PyPI et npm pour détecter les compromissions de la chaîne d'approvisionnement. Interroge les deux registres pour les nouvelles versions, effectue un diff de chaque version par rapport à la précédente, et utilise un LLM (via Cursor Agent CLI) pour classer les diffs comme bénins ou malveillants. Les découvertes malveillantes déclenchent une alerte Slack.
Les deux écosystèmes sont surveillés par défaut. Utilisez --no-pypi ou --no-npm pour en désactiver un.
Chaque écosystème exécute son propre thread d'interrogation mais partage le pipeline d'analyse et d'alertes.
┌─── PyPI ──────────────────────┐ ┌─── npm ───────────────────────┐
│ │ │ │
│ changelog_since_serial() │ │ CouchDB _changes feed │
│ │ │ │ │ │
│ ▼ │ │ ▼ │
│ ┌────────────┐ │ │ ┌────────────┐ │
│ │ All PyPI │─┐ │ │ │ All npm │─┐ │
│ │ events │ │ │ │ │ changes │ │ │
│ └────────────┘ ▼ │ │ └────────────┘ ▼ │
│ hugovk ──► Watchlist │ │ download-counts ─► Watchlist │
│ │ │ │ │ │
│ "new release" events only │ │ new versions since last epoch │
└───────────────┬───────────────┘ └───────────────┬───────────────┘
│ │
▼ ▼
┌───────────────────┐ ┌───────────────────┐
│ Download old + new│ │ Download old + new│
│ (sdist + wheel) │ │ (tarball) │
└───────────────────┘ └───────────────────┘
│ │
└─────────────────┬─────────────────┘
▼
┌───────────────┐
│ Unified diff │
│ report (.md) │
└───────┬───────┘
▼
┌───────────────┐ ◄── LLM analysis
│ Cursor Agent │ (read-only)
│ CLI (ask mode)│
└───────┬───────┘
│
verdict?
│
malicious │
▼
┌───────────────┐
│ Slack alert │
└───────────────┘
L'analyse par LLM est instruite de rechercher :
pip install -r requirements.txt (la bibliothèque standard couvre la plupart de l'outil ; requests est utilisé pour les téléversements Slack)agent, pas l'IDEWindows (PowerShell) :
irm 'https://cursor.com/install?win32=true' | iex
macOS / Linux :
curl https://cursor.com/install -fsS | bash
Vérifiez avec :
agent --version
Vous devez être authentifié auprès de Cursor (agent login ou définir CURSOR_API_KEY).
Placez votre jeton de bot Slack dans etc/slack.json :
{
"url": "https://hooks.slack.com/services/...",
"bot_token": "xoxb-...",
"channel": "C01XXXXXXXX"
}
Le bot a besoin de la portée chat:write sur le canal cible. Le champ channel est l'ID du canal Slack où les alertes sont postées.
# One-shot: analyze releases from the last ~10 minutes
python monitor.py --once
# Continuous: monitor top 1000 packages (both ecosystems), poll every 5 min
python monitor.py --top 1000 --interval 300
# Production: monitor top 15000, alert to Slack
python monitor.py --top 15000 --interval 300 --slack
# npm only, top 5000
python monitor.py --no-pypi --npm-top 5000
# PyPI only
python monitor.py --no-npm
python monitor.py [OPTIONS]
Options :
--top N Nombre de paquets les plus populaires à surveiller par écosystème (défaut : 15000)
--interval SECS Intervalle d'interrogation en secondes (défaut : 300)
--once Passe unique sur les événements récents, puis quitte
--slack Active les alertes Slack pour les découvertes malveillantes
--model MODEL Remplace le modèle LLM (défaut : composer-2-fast)
--debug Active la journalisation DEBUG (inclut la sortie brute de l'agent)
Options PyPI :
--no-pypi Désactive la surveillance PyPI
--serial N Série du changelog PyPI à partir de laquelle commencer
Options npm :
--no-npm Désactive la surveillance npm
--npm-top N Les N meilleurs paquets npm à surveiller (défaut : identique à --top)
--npm-seq N Séquence de réplication npm à partir de laquelle commencer
PyPI et npm s'exécutent chacun dans leur propre thread d'interrogation. L'état d'interrogation (série PyPI, séquence npm + époque) est persistant dans last_serial.yaml afin que le moniteur reprenne là où il s'est arrêté après un redémarrage.
Pipeline PyPI :
--interval secondes, appelle changelog_since_serial() — un seul appel API qui renvoie tous les événements depuis la dernière vérification"new release" correspondant à la liste de surveillancePipeline npm :
replicate.npmjs.com--interval secondes, récupère le flux _changes pour toutes les modifications du registre depuis la dernière séquenceToute la sortie est journalisée à la fois sur la console et dans logs/monitor_YYYYMMDD.log.
# Compare two versions from PyPI
python package_diff.py requests 2.31.0 2.32.0
# Compare two versions from npm
python package_diff.py --npm express 4.18.2 4.19.0
# Save to file
python package_diff.py telnyx 2.0.0 2.1.0 -o telnyx_diff.md
# Compare local archives
python package_diff.py --local old.tar.gz new.tar.gz -n mypackage
Les téléchargements sont effectués directement via les API des registres (API JSON PyPI / registre npm), pas pip ou npm. Cela signifie :
# Analyze a diff file
python analyze_diff.py telnyx_diff.md
# JSON output
python analyze_diff.py telnyx_diff.md --json
# Use a specific model
python analyze_diff.py telnyx_diff.md --model claude-4-opus
Exécute Cursor Agent CLI en --mode ask (lecture seule) avec --trust. L'agent lit le fichier de diff et renvoie un verdict structuré.
Codes de sortie : 0 = bénin, 1 = malveillant, 2 = inconnu/erreur.
# See what's being released right now (last ~10 min)
python pypi_monitor.py --once --top 15000
# Continuous monitoring (console output only, no analysis)
python pypi_monitor.py --top 1000 --interval 120
Utile pour explorer la vélocité des versions PyPI ou déboguer l'API changelog sans exécuter le pipeline d'analyse complet.
# Print top 1000 packages
python top_pypi_packages.py
# Use as a library
from top_pypi_packages import fetch_top_packages
packages = fetch_top_packages(top_n=500)
# [{"project": "boto3", "download_count": 1577565199}, ...]
Le moniteur effectue 1 appel API par intervalle d'interrogation par écosystème (changelog PyPI / _changes npm), plus 2-3 appels par nouvelle version (historique des versions + téléchargements). C'est très léger.
Lorsque le moniteur détecte une version malveillante, il poste sur Slack :
PyPI :
🚨 Alerte chaîne d'approvisionnement : telnyx 4.87.2
Rang : #5 481 des meilleurs paquets PyPI
Verdict : MALVEILLANT
PyPI : https://pypi.org/project/telnyx/4.87.2/
Résumé de l'analyse (tronqué) :
Les modifications apportées à src/telnyx/_client.py implémentent un
comportement obscurci de téléchargement-déchiffrement-exécution
et des effets secondaires d'importation de module.
Une fonction _d() décode des chaînes base64, un énorme blob _p
contient un script d'exfiltration qui télécharge un fichier .wav
depuis http://83.142.209.203:8080/ringtone.wav et extrait une
charge utile cachée via stéganographie...
npm :
🚨 Alerte chaîne d'approvisionnement : axios 0.30.4
Rang : #42 des meilleurs paquets npm
Verdict : MALVEILLANT
npm : https://www.npmjs.com/package/axios/v/0.30.4
Résumé de l'analyse (tronqué) :
1. **Dépendance non standard** — Le bloc `dependencies` inclut
`plain-crypto-js`. Les versions publiées d'axios ne dépendent que de
`follow-redirects`, `form-data` et `proxy-from-env`. Un quatrième
paquet dont le nom ressemble à un **typosquatt de `crypto-js`** est
un signe classique d'un paquet falsifié, pas d'une version normale d'axios.
agent.ask en lecture seule mais sans sandboxing au niveau du système d'exploitation.Les journaux sont écrits à la fois sur stdout et dans logs/monitor_YYYYMMDD.log. Un nouveau fichier est créé chaque jour. Les deux écosystèmes écrivent dans le même fichier, avec les lignes npm préfixées [npm]. Exemple :
2026-03-27 12:01:15 [INFO] Fetching top 15,000 packages from hugovk dataset...
2026-03-27 12:01:16 [INFO] Watchlist loaded: 15,000 packages (dataset updated 2026-03-01 07:34:08)
2026-03-27 12:01:16 [INFO] Fetching top 15,000 npm packages from download-counts dataset...
2026-03-27 12:01:18 [INFO] npm watchlist loaded: 15,000 packages (download-counts 1.0.52)
2026-03-27 12:01:19 [INFO] [pypi] Starting serial: 35,542,068 (from last_serial.yaml) — polling every 300s
2026-03-27 12:01:19 [INFO] [npm] Starting seq: 42,817,503 (from last_serial.yaml) — polling every 300s
2026-03-27 12:06:18 [INFO] [pypi] 2 new watchlist releases detected (serial 35,542,068 -> 35,542,190)
2026-03-27 12:06:18 [INFO] [pypi] Processing fast-array-utils 1.4 (rank #8,231)...
2026-03-27 12:06:18 [INFO] [pypi] Diffing fast-array-utils 1.3 -> 1.4
2026-03-27 12:06:50 [INFO] [pypi] Analyzing diff for fast-array-utils...
2026-03-27 12:07:35 [INFO] [pypi] Verdict for fast-array-utils 1.4: BENIGN
2026-03-27 12:06:20 [INFO] [npm] 1 new watchlist releases detected (seq -> 42,817,612)
2026-03-27 12:06:20 [INFO] [npm] Processing axios 0.30.4 (rank #42)...
2026-03-27 12:06:21 [INFO] [npm] Diffing axios 0.30.3 -> 0.30.4
2026-03-27 12:07:01 [INFO] [npm] Analyzing diff for axios...
2026-03-27 12:07:45 [INFO] [npm] Verdict for axios 0.30.4: MALICIOUS
Ce projet est sous licence MIT. Les sources de données tierces et les avis sont résumés dans NOTICE.txt.
Les contributions sont les bienvenues — voir CONTRIBUTING.md. Ce dépôt suit le Contributor Covenant. Signalez les problèmes de sécurité via SECURITY.md, pas via les issues publiques.
Questions et discussions : Slack de la communauté Elastic.
| Fichier | Objectif |
|---|
monitor.py | Orchestrateur principal — interroge PyPI + npm, diff, analyse, alerte (threads parallèles) |
pypi_monitor.py | Interrogateur de changelog PyPI autonome (utilisé pour l'exploration) |
package_diff.py | Télécharge et diff deux versions de n'importe quel paquet PyPI ou npm |
analyze_diff.py | Envoie un diff à Cursor Agent CLI, analyse le verdict |
top_pypi_packages.py | Récupère et liste les N meilleurs paquets PyPI par nombre de téléchargements |
slack.py | Client API Slack (SendMessage, PostFile) |
etc/slack.json | Identifiants du bot Slack |
last_serial.yaml | État d'interrogation persistant (série PyPI + séquence/époque npm) |
logs/ | Fichiers journaux quotidiens (monitor_YYYYMMDD.log) |
| Source | Quoi | Limites de débit |
|---|
| hugovk/top-pypi-packages | Top 15 000 paquets PyPI par téléchargements sur 30 jours (JSON mensuel) | Aucune (fichier statique) |
PyPI XML-RPC changelog_since_serial() | Flux d'événements PyPI en temps réel | Déprécié mais fonctionnel ; 1 appel par interrogation suffit |
| PyPI JSON API | Métadonnées des paquets, historique des versions, URLs de téléchargement | Généreuses ; utilisé avec parcimonie (1 appel par version) |
| download-counts (nice-registry) | Compteurs de téléchargements mensuels pour chaque paquet npm (counts.json) | Aucune (tarball npm) |
npm CouchDB replication _changes feed | Flux de changement du registre npm en temps réel | Public ; lectures paginées |
| npm registry API | Packuments de paquets, téléchargements de tarballs | Généreuses ; utilisé avec parcimonie |