自动监控顶级 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
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}, ...]
监控器每个轮询间隔每个生态系统进行 1 次 API 调用(PyPI 变更日志 / npm _changes),加上每个新发布版本 2-3 次调用(版本历史 + 下载)。这非常轻量。
当监控器检测到恶意发布时,它会向 Slack 发布消息:
PyPI:
🚨 供应链警报:telnyx 4.87.2
排名:PyPI 前 15,000 名中的 #5,481
判定:恶意
PyPI:https://pypi.org/project/telnyx/4.87.2/
分析摘要(截断):
对 src/telnyx/_client.py 的更改实现了混淆的
下载-解密-执行行为以及模块导入副作用。
一个 _d() 函数解码 base64 字符串,一个巨大的 _p 块包含
一个泄露脚本,该脚本从
http://83.142.209.203:8080/ringtone.wav 下载 .wav 文件,并通过隐写术提取隐藏负载...
npm:
🚨 供应链警报:axios 0.30.4
排名:npm 前 15,000 名中的 #42
判定:恶意
npm:https://www.npmjs.com/package/axios/v/0.30.4
分析摘要(截断):
1. **非标准依赖** — `dependencies` 块包含 `plain-crypto-js`。已发布的 axios 仅依赖 `follow-redirects`、`form-data` 和 `proxy-from-env`。第四个名称看起来像 **`crypto-js` 风格域名仿冒** 的软件包是典型的篡改或伪造软件包标志,不是正常的 axios 发布。
agent CLI。ask 模式运行,但没有操作系统级别的沙箱保护。日志同时写入 stdout 和 logs/monitor_YYYYMMDD.log。每天创建一个新文件。两个生态系统写入同一个文件,npm 行以 [npm] 为前缀。示例:
2026-03-27 12:01:15 [INFO] 正在从 hugovk 数据集获取前 15,000 个软件包...
2026-03-27 12:01:16 [INFO] 监控列表已加载:15,000 个软件包(数据集更新于 2026-03-01 07:34:08)
2026-03-27 12:01:16 [INFO] 正在从 download-counts 数据集获取前 15,000 个 npm 软件包...
2026-03-27 12:01:18 [INFO] npm 监控列表已加载:15,000 个软件包(download-counts 1.0.52)
2026-03-27 12:01:19 [INFO] [pypi] 启动序列号:35,542,068(来自 last_serial.yaml)— 每 300s 轮询一次
2026-03-27 12:01:19 [INFO] [npm] 启动序列:42,817,503(来自 last_serial.yaml)— 每 300s 轮询一次
2026-03-27 12:06:18 [INFO] [pypi] 检测到 2 个新的监控列表发布版本(序列号 35,542,068 -> 35,542,190)
2026-03-27 12:06:18 [INFO] [pypi] 正在处理 fast-array-utils 1.4(排名 #8,231)...
2026-03-27 12:06:18 [INFO] [pypi] 正在对比 fast-array-utils 1.3 -> 1.4
2026-03-27 12:06:50 [INFO] [pypi] 正在分析 fast-array-utils 的差异...
2026-03-27 12:07:35 [INFO] [pypi] fast-array-utils 1.4 判定:良性
2026-03-27 12:06:20 [INFO] [npm] 检测到 1 个新的监控列表发布版本(序列 -> 42,817,612)
2026-03-27 12:06:20 [INFO] [npm] 正在处理 axios 0.30.4(排名 #42)...
2026-03-27 12:06:21 [INFO] [npm] 正在对比 axios 0.30.3 -> 0.30.4
2026-03-27 12:07:01 [INFO] [npm] 正在分析 axios 的差异...
2026-03-27 12:07:45 [INFO] [npm] axios 0.30.4 判定:恶意
本项目基于 MIT 许可协议 授权。第三方数据来源和声明汇总在 NOTICE.txt 中。
欢迎贡献 — 请参阅 CONTRIBUTING.md。本仓库遵循 Contributor Covenant。通过 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 事件数据流 | 已弃用但功能正常;每次轮询一次调用即可 |
| PyPI JSON API | 软件包元数据、版本历史、下载 URL | 宽松;仅在必要时使用(每个发布版本一次调用) |
| download-counts (nice-registry) | 每个 npm 软件包的月度下载量(counts.json) | 无(npm tarball) |
npm CouchDB replication _changes feed | 实时 npm 注册表变更流 | 公共;分页读取 |
| npm registry API | 软件包包信息、tarball 下载 | 宽松;仅在必要时使用 |