Yoyagm/slopguard

GitHub: Yoyagm/slopguard

SlopGuard 是一款零依赖的预安装安全检查工具,可在 pip/npm 安装前检测出 LLM 幻觉包名、拼写抢注包及已知恶意包。

Stars: 0 | Forks: 0

# SlopGuard [![CI](https://github.com/Yoyagm/slopguard/actions/workflows/slopguard-ci.yml/badge.svg)](https://github.com/Yoyagm/slopguard/actions/workflows/slopguard-ci.yml) [![CodeQL](https://static.pigsec.cn/wp-content/uploads/repos/cas/53/539e9a6bf48ad24469a4363bff3aa68124154549e26592783d3d8577f2acbbfc.svg)](https://github.com/Yoyagm/slopguard/actions/workflows/codeql.yml) ![Python](https://img.shields.io/badge/python-3.11%2B-blue) [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE) ![Coverage](https://img.shields.io/badge/coverage-96%25-brightgreen) ![Typing](https://img.shields.io/badge/mypy-strict-blue) ![Lint](https://img.shields.io/badge/lint-ruff-orange) ![Runtime deps](https://img.shields.io/badge/runtime%20deps-0-success) **状态:** 里程碑 3 已完成 (v0.3.0)。1631 个测试通过,全局覆盖率门槛 ≥90% 且关键包覆盖率 ≥95%。mypy strict,ruff (bandit S),import-linter (7 个契约), CI 在 GitHub Actions 上运行。 ## 它是什么以及检测什么 AI 助手经常建议包含不存在包名的 `pip install` 命令。攻击者会预先注册这些 幻觉产生的名称并植入恶意代码——这种技术被称为 *slopsquatting*——这样开发者在 不经验证的情况下直接复制命令时,就会安装攻击者的包。 SlopGuard 在安装之前检测四类风险,且不执行任何 被分析包的代码: | 层 | 评估内容 | 产生的信号 | |---|---|---| | **第 0 层** — 存在性与年龄 | 查询 PyPI JSON API;验证包是否存在以及存在了多少天 | `NONEXISTENT` (如果返回 404 则直接拦截), `NEW_PACKAGE` (软性) | | **第 1 层** — *typosquatting* | 针对PyPI top-N 使用 Damerau-Levenshtein + Jaro-Winkler,无网络,确定性 | `TYPOSQUAT` (硬性), `NAME_UNTRUSTED` (名称过长) | | **第 2 层** — 元数据 | 仅从 PyPI JSON 获取包的质量信号:发布版本、代码库、元数据字段 | `WEAK_METADATA`, `LOW_VERIFIABILITY` (软性,贡献上限为 10 分) | | **第 3 层** — *threat-intel* | OSV.dev 的 `MAL-*` 安全建议 (已确认恶意) + 可选的 depscope 幻觉观察列表 | `MALICIOUS` (覆盖拦截), `KNOWN_HALLUCINATION` (硬性,权重 85), `THREATINTEL_UNVERIFIABLE` (软性) | | **第 4 层** — *基于 LLM 的幻觉面* (选择性启用) | 使用 Claude 针对混淆/拼写错误/捏造的分类法,对处于灰区的名称 (年轻/低信号) 进行分类 | `LLM_HALLUCINATION_SURFACE` (独立通道:至多为 `warn`,绝不 `block`), `LLM_UNAVAILABLE` (信息性) | **零运行时依赖** (仅使用标准库)。PyPI JSON API + OSV.dev (免费, 公开) 以及一个经过 SHA-256 验证的内嵌数据集。**第 4 层 (LLM)** 为 *选择性启用* (需要 `ANTHROPIC_API_KEY`);默认禁用,其余功能无需 LLM 或付款即可运行。 从 **里程碑 4** 开始,SlopGuard 分析两个功能对等的生态系统:**PyPI** 和 **npm** (`package.json`)。系统会根据清单文件名自动检测生态系统,或者 通过 `--ecosystem {pypi,npm}` 强制指定。分层引擎是**生态系统不可知的**:所有 npm↔PyPI 的差异都存在于适配器中。参见 [生态系统:PyPI 和 npm](#ecosistemas-pypi-y-npm-hito-4)。 ## 安装 ``` # 在 repository 内(editable 模式,推荐用于开发) pip install -e . # 使用开发工具 pip install -e ".[dev]" ``` 需要 Python 3.11+。 ## CLI 的使用 ### 基础扫描 ``` slopguard scan requirements.txt slopguard scan pyproject.toml pip freeze | slopguard scan - # lee de stdin en formato pip freeze ``` ### 人类可读输出示例 ``` $ slopguard scan req.txt SlopGuard 0.1.0 — escaneando req.txt (3 dependencias) [BLOCK] reqursts score: 75 | sospechado: requests Capa 1: El nombre se parece a 'requests' (distancia Damerau-Levenshtein 1). Accion sugerida: verificar si quiso escribir 'requests'. [BLOCK] paquete-inexistente-xyz score: — (override: no existe en PyPI) Capa 0: El paquete no existe en PyPI (404). Posible alucinacion de LLM. [ALLOW] boto3 score: 3 Resumen: 1 allow · 0 warn · 2 block · 0 unverifiable Exit code: 2 ``` ### JSON 输出示例 ``` slopguard scan req.txt --format json ``` ``` { "schema_version": "1.0", "tool_version": "0.1.0", "ecosystem": "pypi", "summary": { "total": 3, "allow": 1, "warn": 0, "block": 2, "unverifiable": 0, "exit_code": 2 }, "error_category": null, "results": [ { "name": "paquete-inexistente-xyz", "version_pin": null, "status": "ok", "verdict": "block", "score": null, "suspected_target": null, "error_category": null, "signals": [ { "layer": 0, "code": "nonexistent", "weight": 0, "is_soft": false, "detail": "El paquete no existe en PyPI (404). Posible alucinacion de LLM.", "suspected_target": null } ] }, { "name": "reqursts", "version_pin": null, "status": "ok", "verdict": "block", "score": 75, "suspected_target": "requests", "error_category": null, "signals": [ { "layer": 1, "code": "typosquat", "weight": 60, "is_soft": false, "detail": "El nombre se parece a 'requests' (distancia 1).", "suspected_target": "requests" }, { "layer": 0, "code": "new_package", "weight": 15, "is_soft": true, "detail": "Publicado hace 4 dias (umbral 90).", "suspected_target": null } ] }, { "name": "boto3", "version_pin": null, "status": "ok", "verdict": "allow", "score": 3, "suspected_target": null, "error_category": null, "signals": [] } ] } ``` JSON 输出是稳定的、带版本的 (`schema_version`) 且**没有时间戳** (保证 CI 的确定性)。当判定结果是由于覆盖 (不存在) 或包为 `unverifiable` 时,`score` 字段为 `null`。 ## 第 3 层 — Threat-intel (OSV.dev + 可选的 watchlist) ### 通过 OSV.dev 检测恶意包 第 3 层会查询 [OSV.dev](https://osv.dev) —— 一个公开且免费的 *开源安全*建议数据库 —— 以便在安装前检测**已被确认恶意的** Python 包。 只评估在 PyPI 中存在的包 (第 0 层状态为 `FOUND`);不存在的包已经通过覆盖判定为 `block`,不需要进行网络查询。 **拦截标准:**仅带有 `MAL-` 前缀的安全建议 (例如 `MAL-2025-47868`) 会产生 `MALICIOUS` 信号并触发拦截。一般的漏洞 建议 (`GHSA-*`, `CVE-*`, `PYSEC-*`) 在判定时被**忽略** —— SlopGuard 不是 一个 CVE 扫描器,它是*供应链*的守护者。 一个 `MALICIOUS` 信号会强制 `verdict=block` 并通过覆盖使 `score=null`,具有 高于第 0-2 层任何其他判定的**最高优先级**。如果同时存在恶意和拼写错误抢占 (typosquat),这两个信号都会被报告。 ### 已知幻觉观察列表 (depscope,可选) `depscope-hallucinations` 观察列表是一个**选择性启用**的已知幻觉包名 (来自 LLM 的基准测试语料库) 来源,可在 [depscope.dev](https://depscope.dev) 获取。默认情况下它处于未激活状态: - **当 `enable_watchlist=false` (默认) 时:**不会向 depscope.dev 发起任何查询; 该主机不会被添加到网络的 allowlist 中。 - **当 `enable_watchlist=true` 或使用 `--enable-watchlist` 时:**在运行时获取语料库 (执行 GET 请求,缓存 TTL 为 24 小时)。完全匹配会产生信号 `KNOWN_HALLUCINATION` (硬性信号, 权重 85),从而导致基于分数的 `block`。**包内不会重新分发或内嵌该语料库** (出于对 depscope 的 CC-BY-NC-SA 许可证的尊重)。语料库的归属和许可证信息会显示在输出中。 ### 第 3 层的 Flags ``` --no-layer3 Desactiva completamente la Capa 3 (modo solo-deterministas). Sin red hacia OSV ni depscope. Equivalente al comportamiento del Hito 1. --enable-watchlist Activa la consulta opcional a depscope.dev (watchlist de alucinaciones). Requiere red hacia depscope.dev. ``` 这些 flags 也可以通过 `pyproject.toml` 或 `.slopguard.toml` 进行配置: ``` [tool.slopguard] enable_layer3 = true # default: true enable_watchlist = false # default: false ``` ### 隐私与透明度 (NFR-Priv.3) SlopGuard 遵循最小化数据暴露原则: | 发送内容 | 目标主机 | 触发时机 | |---|---|---| | 规范化名称 (PEP 503) + 生态系统 (`PyPI`) | `api.osv.dev` | 只要 `enable_layer3=true` 且包存在于 PyPI 中 | | 仅不带用户参数的 GET 请求 | `depscope.dev` | 仅当 `enable_watchlist=true` 时 | **绝不发送:**清单文件的内容、本地路径、不必要的固定版本、 用户标识符或任何环境信息。 **无第三方网络模式:**使用 `--no-layer3` (或在配置中设置 `enable_layer3=false`) 以在纯确定性模式下运行。第 0-2 层仅联系 `pypi.org`。 ### 第 3 层的默认配置 | 参数 | 默认值 | 描述 | |---|---|---| | `enable_layer3` | `true` | 启用/禁用第 3 层 | | `osv_host` | `api.osv.dev` | OSV API 的主机地址 | | `osv_ttl_cache_horas` | `6` | 磁盘上 OSV 缓存的 TTL | | `osv_timeout_total_por_lote_s` | `30` | 每批查询的时间预算 | | `osv_reintentos` | `2` | 遇到瞬时错误 (5xx/429) 时的重试次数 | | `osv_batch_max` | `1000` | 每次请求发送给 OSV 的最大包数量 | | `enable_watchlist` | `false` | 启用 depscope watchlist (选择性开启) | | `watchlist_host` | `depscope.dev` | watchlist 的主机地址 | | `watchlist_ttl_cache_horas` | `24` | watchlist 语料库缓存的 TTL | | `watchlist_timeout_total_s` | `30` | 获取语料库的超时时间 | | `threatintel_degraded_status` | `unverifiable` | 当 OSV 无响应时的状态 (`unverifiable` 或 `warn`) | ### 安全降级 如果 OSV.dev 或 depscope.dev 没有响应 (超时、5xx、触及速率限制),第 3 层 **绝不会产生虚假的“一切正常”**:它会发出软性信号 `THREATINTEL_UNVERIFIABLE` 并将依赖项的状态降级为 `unverifiable` (exit code 3),同时保留 第 0-2 层确定性判定的结果。由 typosquat 或不存在导致的 `block` 优先于任何 threat-intel 失败。 ### 输出示例 — bioql (MAL-2025-47868) ``` $ slopguard scan req.txt SlopGuard 0.2.0 — escaneando req.txt (2 dependencias) [BLOCK] bioql score: — (override: malicia confirmada por OSV) Capa 3: Reportado como malicioso — MAL-2025-47868 https://osv.dev/vulnerability/MAL-2025-47868 Accion sugerida: no instalar; paquete reportado como malicioso. [ALLOW] requests score: 2 Resumen: 1 allow · 0 warn · 1 block · 0 unverifiable Exit code: 2 ``` ### JSON 输出示例 (schema_version 1.1) ``` slopguard scan req.txt --format json ``` ``` { "schema_version": "1.1", "tool_version": "0.2.0", "ecosystem": "pypi", "summary": { "total": 2, "allow": 1, "warn": 0, "block": 1, "unverifiable": 0, "exit_code": 2 }, "error_category": null, "results": [ { "name": "bioql", "version_pin": null, "status": "ok", "verdict": "block", "score": null, "suspected_target": null, "error_category": null, "advisories": [ { "id": "MAL-2025-47868", "kind": "malicious", "url": "https://osv.dev/vulnerability/MAL-2025-47868", "source": "osv" } ], "signals": [ { "layer": 3, "code": "malicious", "weight": 0, "is_soft": false, "detail": "Reportado como malicioso por OSV (MAL-2025-47868). No instalar.", "suspected_target": null } ] }, { "name": "requests", "version_pin": null, "status": "ok", "verdict": "allow", "score": 2, "suspected_target": null, "error_category": null, "advisories": [], "signals": [] } ] } ``` `advisories[]` 字段在 schema 1.1 中**始终存在** (如果没有恶意行为则为空)。 此更改是**向后兼容的**:schema 1.0 的消费者会忽略 `advisories` 和 `layer:3` 的信号,不会发生破坏。 ## 第 4 层 — 基于 LLM 的幻觉面 (里程碑 3,选择性启用) 第 4 层使用 LLM (Claude `claude-opus-4-8`) 作为针对 *灰区*名称的**可选证实者**:这些包**存在**但**年轻或信号较低**,并且**没有任何 硬性信号** (typosquats、恶意和不存在的包已由第 0–3 层解决)。LLM 根据已计算的**确定性上下文** (年龄、 元数据、软性信号),将名称与 slopsquatting 研究的分类法进行对比分类 ——`legitimo` (合法)、 `conflacion` (混淆)、`typo` (拼写错误)、`fabricacion` (捏造)。绝不发送清单:仅发送 `名称 + 生态系统 + 上下文`。 **第 4 层的信号绝不拦截。**它在一个独立的权重通道中运行,上限为 50 分;在 `SOFT_CAP=25` 和 `umbral_block=80` 的情况下,带有 LLM 信号的依赖项可达到的最大值是 `25 + 50 = 75 < 80`。它最多只会将包提升至 `warn` (建议“在安装前进行验证”), 绝不会提升至 `block` —— 这是由构造保证的 (*gating* 排除了所有硬性信号 ⇒ `max_hard=0`) 并经过属性测试验证。 它是**选择性启用**的:需要 `--enable-layer4` 和环境变量 `ANTHROPIC_API_KEY`。如果没有密钥 或未加上该 flag,SlopGuard 的行为与里程碑 2 完全一致。如果 LLM 没有响应 (超时、 *拒绝*、错误、达到调用上限),该层将**弃权** (`LLM_UNAVAILABLE`):确定性判定 保持不变并报告该警告,而不会假装“一切正常”。 ### 第 4 层的 Flags ``` --enable-layer4 Activa la Capa 4 (requiere ANTHROPIC_API_KEY). --no-layer4 Fuerza la desactivación (default). --llm-model MODELO Modelo a usar (default: claude-opus-4-8). ``` ### 第 4 层的默认配置 | 参数 | 默认值 | 描述 | |---|---|---| | `enable_layer4` | `false` | 启用/禁用第 4 层 (选择性启用) | | `llm_model` | `claude-opus-4-8` | Claude 模型 | | `gray_edad_max_dias` | `365` | 包被视为“年轻”的最大年龄天数 (*gating* 的分支条件) | | `llm_conf_min` | `0.5` | 发出风险信号所需的最低置信度 | | `llm_max_calls_por_corrida` | `50` | 每次运行的网络调用上限 (缓存命中不计入) | | `llm_ttl_cache_horas` | `168` | LLM 判定缓存的 TTL (标记 `llm-1`) | ## Exit codes (R7) | 代码 | 含义 | 条件 | |---|---|---| | `0` | allow | 所有项结果为 `allow`;无 warn, block 或 unverifiable | | `1` | warn | 至少有 1 个 `warn`,没有 block 也没有 unverifiable (未使用 `--strict`) | | `2` | block | 至少有 1 个 `block` (主导信号);或带有 `--strict` 的任何 `warn` | | `3` | 操作性/unverifiable | 发生全面错误 (清单/配置/数据集) **或** ≥1 个 `unverifiable` 但无 block | **优先级:**`block (2) > 操作性/unverifiable (3) > warn (1) > allow (0)`。 确认的 `block` 优先于未完成的验证。`unverifiable` 即使 exit code 为 2,也始终会在输出中报告。 ## 评分 (ADR-01) 该分数是一个**带有饱和度的累加模型**,它将硬性信号 (基于 名称) 与软性信号 (证实性,有上限) 分开: ``` score = min(100, dura + min(blandas, 25)) ``` | 类别 | 信号 / 条件 | 权重 | |---|---|---| | 硬性 | TYPOSQUAT — DL = 1 (相差一个字符) | 60 | | 硬性 | TYPOSQUAT — DL = 2 | 40 | | 硬性 | TYPOSQUAT — Jaro-Winkler ≥ 0.95 (DL > dl_max) | 30 | | 硬性 | TYPOSQUAT — 0.92 ≤ JW < 0.95 | 25 | | 硬性 | NAME_UNTRUSTED — 长度 > `nombre_max_chars` | 30 | | 软性 | NEW_PACKAGE — 发布少于 `edad_minima_dias` 天 | 15 | | 软性 | 第 2 层 (WEAK_METADATA + LOW_VERIFIABILITY,硬上限) | ≤ 10 | **防误报不变性:**软性信号的最大总和为 25,严格 小于 `umbral_warn` (50)。因此,**仅靠软性信号的任何组合** 都无法产生 `warn` 或 `block`。存在且未触发 typosquat 的包 结果始终为 `allow`。 ## 配置与默认值 (R8) SlopGuard 从 `pyproject.toml` 中的 `[tool.slopguard]` 或从 工作目录下的 `.slopguard.toml` 文件加载配置。CLI 的 flags 优先级 高于文件,而文件优先级高于默认值。 **优先级:**CLI flags > 配置文件 > 默认值 ### 默认值表 | 参数 | 默认值 | 描述 | |---|---|---| | `umbral_block` | `80` | 判定为 `block` 的最低分数 | | `umbral_warn` | `50` | 判定为 `warn` 的最低分数 | | `edad_minima_dias` | `90` | 触发 `NEW_PACKAGE` 信号的最小存在天数 | | `ttl_cache_horas` | `24` | 磁盘缓存的有效期 | | `concurrencia_max` | `8` | 并行网络 Workers (ThreadPoolExecutor) | | `connect_timeout_s` | `5` | TCP 连接超时 (秒) | | `read_timeout_s` | `10` | HTTP 读取超时 (秒) | | `reintentos_red` | `2` | 遇到瞬时错误时的重试次数 (基础退避 0.5秒) | | `timeout_total_por_dep_s` | `30` | 每个依赖项的总时间预算 | | `jw_min` | `0.92` | Jaro-Winkler 相似度的最小阈值 | | `dl_max` | `2` | 触发信号的 Damerau-Levenshtein 最大距离 | | `nombre_max_chars` | `100` | 触发 NAME_UNTRUSTED 的最大名称长度 | | `releases_min` | `1` | 稀少发布版本的阈值 (第 2 层) | | `metadata_faltantes_min` | `2` | 必须缺失的最小元数据字段数 (第 2 层) | | `releases_populares` | `10` | 应用第 2 层上限的发布版本阈值 | | `c2_max_contrib` | `10` | 第 2 层对分数的最大贡献值 | | `max_manifest_bytes` | `5_000_000` | 清单的最大大小 | | `max_deps` | `5000` | 每个清单的最大依赖项数量 | | `max_response_bytes` | `10_000_000` | HTTP 响应限制 (防炸弹) | | `max_json_depth` | `50` | JSON 的最大深度 (防 JSON 炸弹) | | `max_include_depth` | `10` | `-r`/`-c` 包含的最大深度 | ### 通过 `pyproject.toml` 配置 ``` [tool.slopguard] umbral_block = 75 umbral_warn = 45 edad_minima_dias = 60 concurrencia_max = 4 ``` ### 通过 `.slopguard.toml` 配置 ``` umbral_block = 75 umbral_warn = 45 ``` ### 主要 CLI flags ``` --format {human,json} Formato de salida (default: human) --ecosystem {pypi,npm} Fuerza el ecosistema (default: autodetectado por el manifiesto) --no-cache Ignora la cache y no escribe en ella --strict Trata cualquier warn como block (exit 2) --config Ruta explicita al archivo de configuracion --manifest-type {requirements,pyproject,freeze} Fuerza el tipo de manifiesto (solo pypi) --umbral-block N Override del umbral de block --umbral-warn N Override del umbral de warn --edad-minima-dias N Override del umbral de edad --concurrencia N Override del numero de workers --jw-min F Override del umbral Jaro-Winkler --dl-max N Override del umbral Damerau-Levenshtein --no-layer3 Desactiva la Capa 3 (modo solo-deterministas, sin red a OSV) --enable-watchlist Activa la watchlist de alucinaciones depscope (opt-in) ``` ## 生态系统:PyPI 和 npm (里程碑 4) SlopGuard 分析**PyPI** 和 **npm** 且功能对等 (这四个层的运作方式相同; 仅查询的 registry、typosquatting 数据集和命名规则不同)。 **通过清单自动检测** (R1.2): | 清单 | 生态系统 | |---|---| | `package.json` | npm | | `requirements*.txt`,任何 `.txt` | pypi | | `pyproject.toml` | pypi | **显式覆盖** — `--ecosystem {pypi,npm}` 始终优先于自动检测: ``` slopguard scan package.json # npm (autodetectado) slopguard scan deps.txt --ecosystem npm # fuerza npm pese al nombre cat package.json | slopguard scan - --ecosystem npm # stdin EXIGE --ecosystem ``` 对于 **stdin** (`-`),没有可用于推断的名称:**必须**提供 `--ecosystem` (R1.5), 如果缺失,则视为可操作的配置错误 (不会假定默认的生态系统)。 npm 的特殊性: - **Scoped 包** (`@scope/name`):在规范化时会保留 `/` (不进行 PEP 503 式的折叠); 第 1 层仅与**相同 scope** 的候选者进行比较 (防止误报)。 - **非 registry 的 Specifiers** 在扫描时会被**忽略** (不会向 registry 查询):`file:`、 `link:`, `workspace:`, `git://`, `git+…`, `github:`, tarballs `http(s)://` (R2.7)。 - **内嵌的 npm 数据集** (~8000 个名称) 已通过 SHA-256 验证;可以 以可重现的方式重新生成 —— 参见 [npm 数据集操作手册](docs/runbook-dataset-npm.md)。 - 第 0/2 层 `signals[].detail` 的叙述性文本可能会对 npm 依赖显示 "PyPI" (在 [ADR-0001](docs/adr/0001-texto-ecosistema-en-detail-capas-0-2.md) 中记录的外观性技术债务); 但 JSON 中的结构性字段 `ecosystem` **始终**是正确的。 ## 用于 CI 的 JSON 格式 (`schema_version` 1.2) `schema_version` 字段保证了向前兼容性;更改是 **严格累加的**。演进:`1.0` → `1.1` (增加 `advisories[]` 和信号 `layer:3`) → **`1.2`** (增加根级字段 **`ecosystem`**:`"pypi"` | `"npm"`。 之前的字段不会被修改或删除。 每个结果的稳定字段为: ``` name string nombre normalizado (PEP 503) version_pin string|null status "ok" | "unverifiable" verdict "allow" | "warn" | "block" | null (null si unverifiable) score integer 0-100 | null (null si unverifiable, inexistente o MALICIOUS) suspected_target string|null (objetivo del typosquat si aplica) error_category string|null advisories[] array de advisories MAL-* (id, kind, url, source); [] si sin malicia [NUEVO 1.1] signals[] array de seniales con layer/code/weight/is_soft/detail/suspected_target ``` ## 在 pre-commit 和 GitHub Actions 中作为 gate 使用 ### GitHub Actions ``` - name: SlopGuard — gate de supply chain run: slopguard scan requirements.txt --strict --format json # Exit 0: todo ok. Exit 2: block (o warn con --strict). Exit 3: error operacional. ``` ``` - name: SlopGuard con reporte JSON run: | slopguard scan requirements.txt --format json | tee slopguard-report.json # El exit code del proceso es el de slopguard ``` ### pre-commit ``` # .pre-commit-config.yaml repos: - repo: local hooks: - id: slopguard name: SlopGuard — supply chain guard entry: slopguard scan args: [requirements.txt, --strict] language: python pass_filenames: false always_run: true ``` ## 安全属性 - **零包代码执行:**SlopGuard 仅检查 PyPI 元数据 和 OSV 的安全建议。绝不导入、执行或评估被分析包的 代码。不对外部数据使用 `eval`, `exec`, `pickle` 或 `marshal` (已通过 lint 和 AST 测试验证)。 - **加固的 HTTP:**仅使用 HTTPS 并启用活跃的证书验证 (无法关闭), 扩展到 `{pypi.org, api.osv.dev}` 的主机 *allowlist* (仅在 `enable_watchlist=true` 时包含 `depscope.dev`),无跨方案/跨主机的重定向 — 包括 那些来自 `api.osv.dev` 的重定向 (修复 SSRF:重定向处理器会针对 每个实例的有效集合进行验证),受 `max_response_bytes` 限制的*流式*读取,缓解 解压炸弹和深层 JSON 炸弹 (`max_json_depth`)。 - **Fail-closed (第 3 层):**如果 OSV 或 depscope 没有响应,该依赖项将变为 `unverifiable` (exit 3),绝不会是 `allow`。由确定性分层导致的 block 优先于 任何 threat-intel 失败。 - **带有保护的扩展 Allowlist:**`ALLOWED_HOSTS = {pypi.org}` 依然是 经过静态验证的基础常量;`api.osv.dev` 和 (如果适用) `depscope.dev` 会被以明确且受控的方式按实例添加。静态保护 (AST 测试) 会在 CI 中验证基础集合和有效集合。 - **防止信息源投毒:**OSV 的安全建议 ID 在构造任何 URL 之前会使用正则表达式 `^MAL-[0-9A-Za-z-]+$` 进行验证。名称在 被包含进 POST body 之前会针对字符集进行验证。watchlist 语料库在 接收时和从缓存读取时都会进行验证 (字符集、大小上限、schema)。 - **安全的缓存:**仅使用 JSON (绝不使用 pickle),原子写入 (`os.replace`), 权限 0700/0600,带有命名空间的 SHA-256 密钥 (防 *path traversal*),读取时进行 防御性验证。`UNVERIFIABLE` 状态绝不会持久化存储在缓存中。 - **数据集完整性:**内嵌的 top-N 在加载时通过 SHA-256 进行验证。 - **防止输出注入:**任何名称或外部数据 (包括 OSV/depscope 的 ID 和摘要) 在人类可读输出、日志和 JSON 中 都会经过净化处理 (ANSI CSI/SGR,C0/C1 控制字符,CR/LF)。 - **隐私:**仅向 OSV/depscope 发送规范化名称 + 生态系统。绝不发送 清单、本地路径或用户标识符。可通过 `--no-layer3` 完全禁用。 ## 开发 ``` python3.11 -m venv .venv && source .venv/bin/activate pip install -e ".[dev]" # Quality gates(在 commit 前必须全部通过) ruff check . mypy # strict, 82 archivos lint-imports # 5 contratos (core/cli, capas/scoring, source, layer3, + hito1) pytest --cov=slopguard --cov-branch --cov-fail-under=90 ``` CI (`.github/workflows/slopguard-ci.yml`) 会在 Python 3.11 和 3.12 上执行相同的 gates,此外还会将 LaTeX 技术文档编译为 PDF。 ## 技术文档 完整的技术文档 (分层架构、ADR、数据模型、 时序图、EARS 可追溯性) 位于 `docs/slopguard.tex` 中,并作为 CI 的产物 编译为 PDF。
标签:LNA, Python, 云安全监控, 无后门, 逆向工具, 防护工具, 静态分析