
자동화된 공급망 보안 모니터로, PyPI 및 npm 레지스트리를 폴링하고 새 릴리스를 이전 버전과 비교(diff)하며, LLM 분석을 사용하여 악성 코드 변경을 탐지하고 Slack 알림을 보냅니다.
상위 PyPI 및 npm 패키지의 공급망 손상을 자동으로 모니터링합니다. 두 레지스트리를 폴링하여 새 릴리스를 찾고, 각 릴리스를 이전 버전과 비교(diff)한 후, LLM(Cursor Agent CLI 경유)을 사용하여 차이점을 양성 또는 악성으로 분류합니다. 악성 발견 시 Slack 알림이 전송됩니다.
기본적으로 두 생태계가 모두 모니터링됩니다. --no-pypi 또는 --no-npm을 사용하여 하나를 비활성화할 수 있습니다.
각 생태계는 자체 폴링 스레드를 실행하지만 분석 및 알림 파이프라인은 공유합니다.
┌─── 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 │
└───────────────┘
LLM 분석은 다음을 찾도록 프롬프트됩니다:
pip install -r requirements.txt로 런타임 종속성을 설치하세요 (표준 라이브러리로 대부분 커버됨; requests는 Slack 업로드에 사용됨)agent 바이너리Windows (PowerShell):
irm 'https://cursor.com/install?win32=true' | iex
macOS / Linux:
curl https://cursor.com/install -fsS | bash
다음 명령으로 확인하세요:
agent --version
Cursor에 인증되어 있어야 합니다 (agent login 또는 CURSOR_API_KEY 설정).
Slack 봇 토큰을 etc/slack.json에 배치하세요:
{
"url": "https://hooks.slack.com/services/...",
"bot_token": "xoxb-...",
"channel": "C01XXXXXXXX"
}
봇은 대상 채널에 chat:write 범위가 필요합니다. channel 필드는 알림이 게시되는 Slack 채널 ID입니다.
# 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 Number of top packages to watch per ecosystem (default: 15000)
--interval SECS Poll interval in seconds (default: 300)
--once Single pass over recent events, then exit
--slack Enable Slack alerts for malicious findings
--model MODEL Override LLM model (default: composer-2-fast)
--debug Enable DEBUG logging (includes agent raw output)
PyPI options:
--no-pypi Disable PyPI monitoring
--serial N PyPI changelog serial to start from
npm options:
--no-npm Disable npm monitoring
--npm-top N Top N npm packages to watch (default: same as --top)
--npm-seq N npm replication sequence to start from
PyPI와 npm은 각각 자체 폴링 스레드에서 실행됩니다. 폴링 상태(PyPI 시리얼, npm 시퀀스 + 에포크)는 last_serial.yaml에 저장되어 재시작 후에도 중단된 지점에서 재개됩니다.
PyPI 파이프라인:
--interval초마다 changelog_since_serial() 호출 — 마지막 확인 이후의 모든 이벤트를 반환하는 단일 API 호출"new release" 이벤트만 필터링npm 파이프라인:
replicate.npmjs.com에서 현재 CouchDB 복제 시퀀스 읽기--interval초마다 마지막 시퀀스 이후의 모든 레지스트리 변경 사항에 대해 _changes 피드 가져오기모든 출력은 콘솔과 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
다운로드는 pip 또는 npm이 아닌 레지스트리 API(PyPI JSON API / npm 레지스트리)를 통해 직접 수행됩니다. 이는 다음을 의미합니다:
# 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
Cursor Agent CLI를 --mode ask(읽기 전용) 및 --trust로 실행합니다. 에이전트가 차이점 파일을 읽고 구조화된 판정을 반환합니다.
종료 코드: 0 = 양성, 1 = 악성, 2 = 알 수 없음/오류.
# 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
전체 분석 파이프라인을 실행하지 않고 PyPI 릴리스 속도를 탐색하거나 changelog API를 디버깅하는 데 유용합니다.
# 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}, ...]
모니터는 생태계당 폴링 간격마다 1회 API 호출(PyPI changelog / npm _changes)과 새 릴리스당 2-3회 호출(버전 기록 + 다운로드)을 수행합니다. 이는 매우 가벼운 부하입니다.
모니터가 악성 릴리스를 감지하면 Slack에 다음을 게시합니다:
PyPI:
🚨 Supply Chain Alert: telnyx 4.87.2
Rank: #5,481 of top PyPI packages
Verdict: MALICIOUS
PyPI: https://pypi.org/project/telnyx/4.87.2/
Analysis summary (truncated):
The changes to src/telnyx/_client.py implement obfuscated
download-decrypt-execute behavior and module-import side effects.
A _d() function decodes base64 strings, a massive _p blob contains
an exfiltration script that downloads a .wav file from
http://83.142.209.203:8080/ringtone.wav and extracts a hidden
payload via steganography...
npm:
🚨 Supply Chain Alert: axios 0.30.4
Rank: #42 of top npm packages
Verdict: MALICIOUS
npm: https://www.npmjs.com/package/axios/v/0.30.4
Analysis summary (truncated):
1. **Non-standard dependency** — The `dependencies` block includes `plain-crypto-js`. Published axios only depends on `follow-redirects`, `form-data`, and `proxy-from-env`. A fourth package whose name looks like a **`crypto-js`–style typosquat** is a classic sign of a tampered or fake package, not a normal axios release.
agent CLI에 의존합니다.ask 모드로 실행되지만 OS 수준 샌드박싱은 없습니다.로그는 stdout과 logs/monitor_YYYYMMDD.log 모두에 기록됩니다. 매일 새 파일이 생성됩니다. 두 생태계 모두 동일한 파일에 기록되며 npm 줄은 [npm] 접두사가 붙습니다. 예:
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
이 프로젝트는 MIT 라이선스 하에 라이선스가 부여됩니다. 타사 데이터 소스 및 고지 사항은 NOTICE.txt에 요약되어 있습니다.
기여를 환영합니다 — CONTRIBUTING.md를 참조하세요. 이 저장소는 기여자 규약을 따릅니다. 보안 문제는 공개 이슈가 아닌 SECURITY.md를 통해 보고해 주세요.
질문 및 토론: Elastic 커뮤니티 Slack.
| 파일 | 목적 |
|---|
monitor.py | 메인 오케스트레이터 — PyPI + npm 폴링, 차이점 비교, 분석, 알림 (병렬 스레드) |
pypi_monitor.py | 독립 실행형 PyPI 변경 로그 폴러 (탐색용) |
package_diff.py | 모든 PyPI 또는 npm 패키지의 두 버전 다운로드 및 차이점 비교 |
analyze_diff.py | Cursor Agent CLI에 차이점 전송, 판정 분석 |
top_pypi_packages.py | 다운로드 수 기준 상위 N개 PyPI 패키지 가져오기 및 목록 출력 |
slack.py | Slack API 클라이언트 (SendMessage, PostFile) |
etc/slack.json | Slack 봇 자격 증명 |
last_serial.yaml | 지속형 폴링 상태 (PyPI 시리얼 + npm 시퀀스/에포크) |
logs/ | 일일 로그 파일 (monitor_YYYYMMDD.log) |
| 소스 | 내용 | 속도 제한 |
|---|
| hugovk/top-pypi-packages | 30일 다운로드 기준 상위 15,000개 PyPI 패키지 (월별 JSON) | 없음 (정적 파일) |
PyPI XML-RPC changelog_since_serial() | 실시간 PyPI 이벤트 파이어호스 | 더 이상 사용되지 않지만 작동함; 폴링당 1회 호출 문제 없음 |
| PyPI JSON API | 패키지 메타데이터, 버전 기록, 다운로드 URL | 넉넉함; 드물게 사용 (릴리스당 1회 호출) |
| download-counts (nice-registry) | 모든 npm 패키지의 월별 다운로드 수 (counts.json) | 없음 (npm tarball) |
npm CouchDB replication _changes feed | 실시간 npm 레지스트리 변경 스트림 | 공개; 페이지네이션 읽기 |
| npm registry API | 패키지 문서, tarball 다운로드 | 넉넉함; 드물게 사용 |