elastic/supply-chain-monitor

GitHub: elastic/supply-chain-monitor

利用 LLM 自动监控 PyPI 和 npm 顶级包的版本变更,通过 diff 分析检测软件供应链攻击并发出告警。

Stars: 257 | Forks: 25

# 软件供应链监控 [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) 自动监控顶级的 **PyPI** 和 **npm** 包以防范供应链攻击。轮询两个注册中心以获取新版本,将每个版本与前一个版本进行 diff 比较,并使用 LLM(通过 [Cursor Agent CLI](https://cursor.com/docs/cli/overview))将 diff 结果分类为 **良性** 或 **恶意**。检测到恶意行为会触发 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 分析会收到提示以查找以下内容: - 混淆代码(base64、exec、eval、XOR、编码字符串) - 向意外主机发起的网络调用 - 对启动/持久化位置的文件系统写入 - 进程生成和 shell 命令 - 媒体文件中的隐写术或数据隐藏 - 凭据和 token 窃取 - Typosquatting(仿冒)指标 ## 前置条件 - **Python 3.9+** — 使用 `pip install -r requirements.txt` 安装运行时依赖(标准库已包含该工具的大部分功能;`requests` 用于 Slack 上传) - **Cursor Agent CLI** — 独立的 `agent` 二进制文件,而非 IDE ### 安装 Cursor Agent CLI **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 配置 将您的 Slack bot token 放在 `etc/slack.json` 中: ``` { "url": "https://hooks.slack.com/services/...", "bot_token": "xoxb-...", "channel": "C01XXXXXXXX" } ``` 该 bot 需要在目标 channel 上具有 `chat:write` 权限。`channel` 字段是发布警报的 Slack channel ID。 ## 快速开始 ``` # 单次:分析过去约10分钟内的发布 python monitor.py --once # 持续:监控排名前1000的包(两个 ecosystem),每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,diff 对比,分析,警报(并行线程) | | `pypi_monitor.py` | 独立的 PyPI 更新日志轮询器(用于探索) | | `package_diff.py` | 下载并对任意 PyPI 或 npm 包的两个版本进行 diff 对比 | | `analyze_diff.py` | 将 diff 发送给 Cursor Agent CLI,解析判定结果 | | `top_pypi_packages.py` | 按下载量获取并列出前 N 个 PyPI 包 | | `slack.py` | Slack API 客户端(SendMessage, PostFile) | | `etc/slack.json` | Slack bot 凭据 | | `last_serial.yaml` | 持久化的轮询状态(PyPI 序列号 + npm 序列/epoch) | | `logs/` | 每日日志文件(`monitor_YYYYMMDD.log`) | ## 使用详情 ### monitor.py — 主编排器 ``` 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 序列 + epoch)被持久化到 `last_serial.yaml` 中,以便监控器在重启后能从上次中断的位置恢复。 **PyPI 管道:** 1. 从 [hugovk/top-pypi-packages](https://hugovk.github.io/top-pypi-packages/) 数据集加载前 N 个包作为监控列表 2. 连接到 PyPI 的 XML-RPC API 并获取当前序列号 3. 每隔 `--interval` 秒,调用 `changelog_since_serial()` —— 这是一个单次 API 调用,返回上次检查以来的所有事件 4. 筛选出与监控列表匹配的 `"new release"` 事件 5. 对于每个新版本:下载旧版本 + 新版本(当两者都存在时包括 sdist 和 wheel),进行 diff,通过 LLM 分析,如果检测到恶意则在 Slack 发出警报 **npm 管道:** 1. 从 [download-counts](https://www.npmjs.com/package/download-counts) 数据集加载前 N 个包(如果失败则回退到 npm search API) 2. 从 `replicate.npmjs.com` 读取当前的 CouchDB 复制序列 3. 每隔 `--interval` 秒,获取自上次序列以来所有注册中心变更的 `_changes` feed 4. 根据监控列表筛选出发生变更的包,并检查在上次轮询 epoch 之后发布的版本 5. 对于每个新版本:从 npm 注册中心下载新旧 tarball 包,进行 diff,通过 LLM 分析,如果检测到恶意则在 Slack 发出警报 所有输出都会同时记录到控制台和 `logs/monitor_YYYYMMDD.log` 中。 ### package_diff.py — 包差异比较器 ``` # 比较来自 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 # 比较本地 archives python package_diff.py --local old.tar.gz new.tar.gz -n mypackage ``` 下载直接通过注册中心 API(PyPI JSON API / npm registry)进行,而不是使用 pip 或 npm。这意味着: - 下载**不需要依赖 pip/npm** - **跨平台** — 可以在 Windows 上下载并 diff 仅限 Linux 的包 - PyPI:优先使用 wheel(如果可用则选择纯 Python 包),回退到 sdist - npm:直接从 registry 下载 tarballs ### analyze_diff.py — LLM 裁决 ``` # 分析 diff 文件 python analyze_diff.py telnyx_diff.md # JSON 输出 python analyze_diff.py telnyx_diff.md --json # 使用特定的 model python analyze_diff.py telnyx_diff.md --model claude-4-opus ``` 使用 `--trust` 参数以 `--mode ask`(只读)模式运行 Cursor Agent CLI。agent 会读取 diff 文件并返回结构化的判定结果。 退出代码:`0` = 良性,`1` = 恶意,`2` = 未知/错误。 ### pypi_monitor.py — 独立轮询器 ``` # 查看当前正在发布的内容(过去约10分钟内) python pypi_monitor.py --once --top 15000 # 持续监控(仅控制台输出,不进行分析) python pypi_monitor.py --top 1000 --interval 120 ``` 用于探索 PyPI 发布速度或调试 changelog API,而无需运行完整的分析管道。 ### top_pypi_packages.py — 包排名 ``` # 打印排名前1000的包 python top_pypi_packages.py ``` ``` # 作为 library 使用 from top_pypi_packages import fetch_top_packages packages = fetch_top_packages(top_n=500) # [{"project": "boto3", "download_count": 1577565199}, ...] ``` ## 数据来源 | 来源 | 内容 | 速率限制 | |--------|------|-------------| | [hugovk/top-pypi-packages](https://hugovk.github.io/top-pypi-packages/) | 按 30 天下载量排名的前 15,000 个 PyPI 包(每月 JSON) | 无(静态文件) | | [PyPI XML-RPC](https://warehouse.pypa.io/api-reference/xml-rpc.html) `changelog_since_serial()` | 实时 PyPI 事件流 | 已弃用但仍可用;每次轮询 1 次调用完全没问题 | | [PyPI JSON API](https://warehouse.pypa.io/api-reference/json.html) | 包元数据、版本历史、下载 URL | 宽松;使用频率低(每个版本 1 次调用) | | [download-counts](https://www.npmjs.com/package/download-counts) (nice-registry) | 每个 npm 包的每月下载计数(`counts.json`) | 无(npm tarball) | | [npm CouchDB replication](https://replicate.npmjs.com) `_changes` feed | 实时 npm 注册中心变更流 | 公开;分页读取 | | [npm registry API](https://registry.npmjs.org) | 包 packuments、tarball 下载 | 宽松;使用频率低 | 监控器**每个生态系统在每个轮询间隔仅发起 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. ``` ## 局限性 - 每个生态系统线程内的版本是按顺序分析的。在版本发布量较大时,会出现处理积压。 - **必须使用 Cursor Agent CLI** — 分析依赖于有效的 Cursor 订阅以及经过身份验证的 `agent` CLI。 - **沙盒模式**(文件系统隔离)仅在 macOS/Linux 上可用。在 Windows 上,agent 以只读的 `ask` 模式运行,但没有操作系统级别的沙盒保护。 - **监控列表是静态的** — 在启动时从 hugovk (PyPI) 和 download-counts (npm) 数据集加载一次。需要重启才能刷新。 - **npm _changes 间隔保护** — 如果保存的 npm 序列落后于 registry head 超过 10,000 个变更,监控器将重置到 head 以避免漫长的追赶过程。间隔期间的版本将会被遗漏。 ## 日志记录 日志会同时写入标准输出和 `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 许可证](LICENSE) 授权。第三方数据来源和声明已在 [NOTICE.txt](NOTICE.txt) 中总结。 欢迎贡献代码 — 详见 [CONTRIBUTING.md](CONTRIBUTING.md)。本仓库遵循 [Contributor Covenant](CODE_OF_CONDUCT.md)。请通过 [SECURITY.md](SECURITY.md) 报告安全问题,不要使用公开的 issues。 问题与讨论:[Elastic community Slack](https://ela.st/slack)。
标签:CouchDB, DNS 反向解析, GNU通用公共许可证, IP 地址批量处理, LLM, Node.js, npm, PyPI, Python, Slack, Unmanaged PE, 代码差异分析, 包管理器, 开源生态, 无后门, 统一API, 网络信息收集, 网络调试, 自动化, 警报通知, 逆向工具