自动监控顶级 PyPI 和 npm 软件包是否存在供应链被攻陷的风险。轮询这两个注册表的新发布版本,将每个发布版本与其前身进行差异对比,并使用 LLM(通过 Cursor Agent CLI)将差异分类为 良性 或 恶意。恶意发现会触发 Slack 警报。
默认情况下监控两个生态系统。使用 --no-pypi 或 --no-npm 禁用其中一个。
每个生态系统运行自己的轮询线程,但共享分析和警报管道。
┌─── PyPI ──────────────────────┐ ┌─── npm ───────────────────────┐
│ │ │ │
│ changelog_since_serial() │ │ CouchDB _changes feed │
│ │ │ │ │ │
│ ▼ │ │ ▼ │
│ ┌────────────┐ │ │ ┌────────────┐ │
│ │ 所有 PyPI │─┐ │ │ │ 所有 npm │─┐ │
│ │ 事件 │ │ │ │ │ 变更 │ │ │
│ └────────────┘ ▼ │ │ └────────────┘ ▼ │
│ hugovk ──► 监控列表 │ │ download-counts ─► 监控列表 │
│ │ │ │ │ │
│ 仅"新发布"事件 │ │ 自上次纪元以来的新版本 │
└───────────────┬───────────────┘ └───────────────┬───────────────┘
│ │
▼ ▼
┌───────────────────┐ ┌───────────────────┐
│ 下载旧版 + 新版 │ │ 下载旧版 + 新版 │
│ (sdist + wheel) │ │ (tarball) │
└───────────────────┘ └───────────────────┘
│ │
└─────────────────┬─────────────────┘
▼
┌───────────────┐
│ 统一差异报告 │
│ (.md) │
└───────┬───────┘
▼
┌───────────────┐ ◄── LLM 分析
│ Cursor Agent │ (只读)
│ CLI (ask 模式)│
└───────┬───────┘
│
判定结果?
│
恶意 │
▼
┌───────────────┐
│ Slack 警报 │
└───────────────┘
LLM 分析提示要查找以下内容:
pip install -r requirements.txt 安装运行时依赖项(标准库覆盖了大多数工具;requests 用于 Slack 上传)agent 二进制文件,不是 IDEWindows (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。
# 单次运行:分析最近约 10 分钟内的发布版本
python monitor.py --once
# 持续监控:监控前 1000 个软件包(两个生态系统),每 5 分钟轮询一次
python monitor.py --top 1000 --interval 300
# 生产环境:监控前 15000 个,向 Slack 发送警报
python monitor.py --top 15000 --interval 300 --slack
# 仅 npm,前 5000 个
python monitor.py --no-pypi --npm-top 5000
# 仅 PyPI
python monitor.py --no-npm
| 文件 | 用途 |
|---|---|
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) |
python monitor.py [选项]
选项:
--top N 每个生态系统监控的顶级软件包数量(默认:15000)
--interval SECS 轮询间隔(秒,默认:300)
--once 对最近事件执行单次遍历,然后退出
--slack 对恶意发现启用 Slack 警报
--model MODEL 覆盖 LLM 模型(默认:composer-2-fast)
--debug 启用 DEBUG 日志(包含 agent 原始输出)
PyPI 选项:
--no-pypi 禁用 PyPI 监控
--serial N PyPI 变更日志起始序列号
npm 选项:
--no-npm 禁用 npm 监控
--npm-top N 监控的前 N 个 npm 软件包(默认:与 --top 相同)
--npm-seq N npm 复制起始序列号
PyPI 和 npm 各自运行在自己的轮询线程中。轮询状态(PyPI 序列号,npm 序列 + 纪元)保存到 last_serial.yaml,以便监控器在重启后从中断处继续。
PyPI 流水线:
--interval 秒,调用 changelog_since_serial() — 一个返回自上次检查以来所有事件的单次 API 调用"new release" 事件npm 流水线:
replicate.npmjs.com 读取当前的 CouchDB 复制序列--interval 秒,获取自上次序列以来所有注册表变更的 _changes feed所有输出同时记录到控制台和 logs/monitor_YYYYMMDD.log。
# 比较 PyPI 的两个版本
python package_diff.py requests 2.31.0 2.32.0
# 比较 npm 的两个版本
python package_diff.py --npm express 4.18.2 4.19.0
# 保存到文件
python package_diff.py telnyx 2.0.0 2.1.0 -o telnyx_diff.md
# 比较本地归档
python package_diff.py --local old.tar.gz new.tar.gz -n mypackage
直接通过注册表 API(PyPI JSON API / npm 注册表)进行下载,而不是通过 pip 或 npm。这意味着:
# 分析差异文件
python analyze_diff.py telnyx_diff.md
# JSON 输出
python analyze_diff.py telnyx_diff.md --json
# 使用特定模型
python analyze_diff.py telnyx_diff.md --model claude-4-opus
以 --mode ask(只读)运行 Cursor Agent CLI,并带有 --trust。agent 读取差异文件并返回结构化判定。
退出代码:0 = 良性,1 = 恶意,2 = 未知/错误。
# 查看当前正在发布的内容(最近约 10 分钟)
python pypi_monitor.py --once --top 15000
# 持续监控(仅控制台输出,无分析)
python pypi_monitor.py --top 1000 --interval 120
用于探索 PyPI 发布速度或调试变更日志 API,无需运行完整的分析流水线。
# 打印前 1000 个软件包
python top_pypi_packages.py
# 作为库使用
from top_pypi_packages import fetch_top_packages
packages = fetch_top_packages(top_n=500)
# [{"project": "boto3", "download_count": 1577565199}, ...]
| 来源 | 内容 | 速率限制 |
|---|---|---|
| hugovk/top-pypi-packages | 按 30 天下载量排序的前 15,000 个 PyPI 软件包(每月 JSON) | 无(静态文件) |
PyPI XML-RPC changelog_since_serial() | 实时 PyPI 事件数据流 | 已弃用但功能正常;每次轮询一次调用即可 |
| PyPI JSON API | 软件包元数据、版本历史、下载 URL | 宽松;仅在必要时使用(每个发布版本一次调用) |
| download-counts (nice-registry) | 每个 npm 软件包的月度下载量(counts.json) | 无(npm tarball) |
npm CouchDB replication _changes feed | 实时 npm 注册表变更流 | 公共;分页读取 |
| npm registry API | 软件包包信息、tarball 下载 | 宽松;仅在必要时使用 |
监控器每个轮询间隔每个生态系统进行 1 次 API 调用(PyPI 变更日志 / npm _changes),加上每个新发布版本 2-3 次调用(版本历史 + 下载)。这非常轻量。