duriantaco/ca9

GitHub: duriantaco/ca9

CA9 是一个本地证据引擎,用于通过静态和动态分析确定 Python 依赖中 CVE 的可达性,减少安全告警疲劳。

Stars: 5 | Forks: 0

ca9 - evidence-backed Python package security

ca9

用于 Python 包和 SCA 告警的本地化、基于证据的安全层。

Python 3.10+ License: MPL-2.0 PyPI Minimal Dependencies Skylos A+ (99)

## 问题所在 你的 SCA 工具(Snyk、Dependabot、Trivy、pip-audit、OSV 或其他扫描器)会标记出依赖树中的每一个 CVE。你收到了 60 个告警。你的团队手忙脚乱。但其中很多 CVE 存在于你的应用**从未导入、从未调用、也从未执行过**的代码中。 你在为那些从未使用的函数、那些你甚至不知道自己拥有的包、那些你的应用永远不会触及的代码路径修补漏洞。 这是在浪费工程时间。这是告警疲劳。这就是真正的漏洞被忽视的原因。 ## ca9 做什么 ca9 是一个面向 Python 包和开源依赖风险的本地优先安全层。 目前,它最强大的路径是将 CVE 告警转化为基于证据的修复、抑制或调查决策。较新的清单路径可以规范化包、制品、锁文件证据和依赖边,使得 ca9 能够超越仅限 CVE 的可达性分析,同时不强制依赖任何解析器或包管理器。 它接收你的 CVE 列表,并为每个漏洞回答一个问题:**这段代码真的能从你的应用中到达吗?** ``` pip install ca9[cli] ca9 scan --repo . --coverage coverage.json ``` 对于包清单和供应链证据,ca9 可以原生读取项目清单文件,并在存在时使用 `fyn.lock` 或 npm 的 `package-lock.json`: ``` ca9 inventory --repo . -f json ca9 vet --repo . -f json ``` 对于代理式分诊,ca9 可以将来自 CodeQL 或 Semgrep 等工具的 SARIF 报告规范化为 `ca9.evidence.v1` 证据报告: ``` ca9 ingest-sarif codeql.sarif --repo . -f json ``` ``` CVE ID Package Severity Verdict -------------------------------------------------------------- GHSA-cpwx-vrp4-4pq7 Jinja2 high REACHABLE GHSA-frmv-pr5f-9mcr Django critical UNREACHABLE (static) GHSA-mrwq-x4v8-fh7p Pygments medium UNREACHABLE (dynamic) -------------------------------------------------------------- Total: 61 | Reachable: 25 | Unreachable: 36 | Inconclusive: 0 59% of flagged CVEs are unreachable — only 25 of 61 require action ``` 消除了 36 个 CVE。无需手动分诊。无需猜测。 ## 工作原理 ca9 结合仓库证据和安全公告元数据来确定漏洞代码是否可达: **1. 静态分析(AST 导入跟踪)** — 解析仓库中的每个 Python 文件并跟踪 `import` 语句。如果某个有漏洞的包从未被导入,则它是不可达的。 **2. 依赖清单** — 使用声明的依赖、报告元数据、本地包元数据、`fyn.lock` 和 npm `package-lock.json`(如果可用)来区分直接依赖、传递依赖、已导入依赖和未使用依赖。 **3. 动态分析(coverage.py)** — 检查漏洞代码在测试套件中是否实际*被*执行。某个包可能被导入了,但其特定的漏洞函数可能从未被调用。 **4. 公告规范化** — 在输入数据提供的情况下,保留来源、别名、CWE/CPE ID、时间戳和缓存新鲜度,以便证据可以追溯到原始告警。 ``` For each CVE: Is the affected version installed or declared? ├── NO → UNREACHABLE (static) └── YES → Is the package, affected submodule, or known vulnerable API used? ├── NO, with enough graph/import evidence → UNREACHABLE (static) ├── YES, and runtime/coverage confirms execution → REACHABLE ├── YES, but coverage shows no affected execution → UNREACHABLE (dynamic) └── Not enough evidence → INCONCLUSIVE ``` ca9 是**保守的** — 它仅在能够证明的情况下才将某些东西标记为不可达。每个判定都附带证据线索和置信度分数,让你能确切了解 ca9 得出结论的原因。 ## ca9 的定位 ca9 并不取代你的 SCA 工具。它为你已有的漏洞数据添加了本地化、证据优先的可达性分析。 | | ca9 | 仅告警的 SCA 输出 | 托管式可达性平台 | |---|---|---|---| | **本地分析** | 在你的仓库/CI 中运行 | 各不相同 | 通常需要上传源代码或导入托管项目 | | **直接 OSV 扫描** | 是 — `ca9 scan` 直接查询 OSV.dev | 并非总是 | 各不相同 | | **SCA 报告解析** | Snyk, Dependabot, Trivy, pip-audit | 各工具原生支持 | 平台特定 | | **包清单** | 原生清单加上可选的 `fyn.lock` 和 npm `package-lock.json` 制品及依赖边 | 各不相同 | 各不相同 | | **静态 + 动态证据** | 导入、依赖图、覆盖率、API 使用情况 | 通常是包级告警 | 因供应商和集成方式而异 | | **开放输出** | JSON, SARIF, OpenVEX, Markdown, HTML, 修复方案, 行动计划 | 供应商特定 | 平台特定 | | **置信度/证据线索** | 每个判定的结构化证据 | 有限 | 各不相同 | | **运行时依赖** | `packaging` 核心依赖;可选的 CLI/MCP 额外包 | 各不相同 | 托管服务 | **当你想要一个开放的、本地的 Python 可达性层,用于 CVE 分诊、CI 门禁、SARIF 上传、OpenVEX 生成或 SBOM 富化时,请使用 ca9。** ## 真实世界案例 ### Django REST Framework — 37 个 CVE,19% 噪声 一个专注的库,真实使用了其大部分依赖。即便如此,ca9 仍然发现了 7 个 CVE,它们存在于已安装但从未导入的包(redis, sentry-sdk, pip)中: ``` $ ca9 scan --repo /path/to/drf -v GHSA-g92j-qhmh-64v2 sentry-sdk low UNREACHABLE (static) -> 'sentry-sdk' is not imported and not a dependency of any imported package GHSA-8fww-64cx-x8p5 redis high UNREACHABLE (static) -> 'redis' is not imported and not a dependency of any imported package ... Total: 37 | Reachable: 0 | Unreachable: 7 | Inconclusive: 30 ``` ### 依赖臃肿的 Flask 应用 — 61 个 CVE,59% 噪声 一个只导入了 4 个包,却在 `requirements.txt` 中固定了 19 个包(包括“以防万一”添加的 Django, tornado, Pygments)的 Flask 应用: ``` $ ca9 scan --repo demo/ --coverage demo/coverage.json Total: 61 | Reachable: 25 | Unreachable: 36 | Inconclusive: 0 59% of flagged CVEs are unreachable — only 25 of 61 require action ``` 仅 Django 就带来了 21 个纯属噪声的 CVE。 **规律:** ca9 的价值随着你的依赖列表臃肿程度而增长 — 而在企业代码库中,这通常是*非常*臃肿的。 ## 快速开始 ### 扫描仓库依赖版本(无需 SCA 工具) ``` pip install ca9[cli] ca9 scan --repo . ``` 这会解析目标仓库的精确依赖版本并查询 [OSV.dev](https://osv.dev)。ca9 默认不使用环境中的 Python 环境,除非你显式传递 `--allow-env-fallback`,这确保了 CI 扫描与仓库证据绑定。无需 Snyk,无需 Dependabot,无需配置文件。 ### 检查包清单和锁文件证据 ``` ca9 inventory --repo . -f json ``` 当存在 `fyn.lock` 或 npm `package-lock.json` 时,ca9 会直接读取它,并包含包版本、直接/传递依赖边、依赖组、制品 URL、哈希或完整性值、注册源和来源证据。如果没有锁文件,ca9 会回退到使用原生 Python 清单读取器来处理 `pyproject.toml`、`requirements*.txt`、`Pipfile`、`uv.lock` 和 `poetry.lock`。 ### 运行供应链风险检查 ``` ca9 vet --repo . ca9 vet --repo . --malware-query ca9 vet --repo . --scan-artifacts ca9 vet --repo . --scan-workflows ca9 vet --repo . --internal-package 'acme-*' --private-index https://packages.acme.internal/simple ca9 vet --repo . --deny-license AGPL-3.0 --deny-license GPL-3.0 ``` `ca9 vet` 对规范化的包清单评估本地供应链风险信号:不受信任的包索引、缺失的制品哈希、缺失的制品元数据、仅源代码安装风险以及可变的包源。使用 `--malware-query` 时,ca9 还会查询 OSV 中的已知恶意包公告,例如 `MAL-*`、`PYSEC-MAL-*` 以及跨 PyPI 和 npm 的标记为恶意软件的 GHSA/OSV 记录。使用 `--scan-workflows` 时,ca9 会检查 GitHub Actions 工作流,寻找风险 token 作用域、OIDC 写入访问、`pull_request_target` 信任边界模式、可变 action 引用、缓存信任边界、源代码克隆命令、编码的 shell 执行、云元数据探测以及广泛的凭证文件收割模式。 来自不受信任索引的直接依赖、已知恶意包和高风险的工作流模式是阻断性发现;较弱的本地信号则作为警告或调查项。 使用 `--scan-artifacts` 时,ca9 默认仅下载带有哈希的锁文件制品,验证哈希,安全解包 wheel/sdist 而不执行代码,并运行类 GuardDog 的静态启发式检查,查找可疑的 `.pth` 启动执行、安装时 `setup.py` 执行、启动自定义钩子、凭证/网络外泄、导入时风险行为、静默进程执行以及编码的有效载荷执行。 ### 重放真实事件测试用例 ``` python scripts/incident_replay.py --strict --format table ``` ca9 保留了用于 npm 包被入侵、PyPI 导入时恶意软件以及 GitHub-token 被入侵场景的真实事件测试用例。当前的测试矩阵是实事求是的:在测试用例能证明的情况下覆盖了恶意软件公告和工作流风险模式,而对于包 tarball、导入时恶意软件以及身份/审计日志等方面仍是部分覆盖。 对于依赖混淆控制,请使用 `--internal-package` 指定一个或多个私有包名称模式,并使用 `--private-index` 指定这些包允许从中解析的索引。对于许可证策略,请使用 `--deny-license`;ca9 读取 wheel/sdist 元数据,阻断被拒绝的直接依赖,同时警告或调查较弱的情况。 ### 即时可用的供应链演示 当你需要报告截图或 JSON 制品而不依赖实时可疑仓库时,请使用本地演示测试用例: ``` bash demo/supply_chain/run_demo.sh ``` 测试用例生成一个带有本地、哈希固定的 wheel 制品的 `fyn.lock`,然后运行 `ca9 vet` 进行制品扫描、依赖混淆策略和拒绝许可证策略检查。底层门控因为发现项是预期的阻断性而返回 `1`;包装器仍然会写入 `demo/supply_chain/ca9-vet.json` 以供截图和 CI 制品示例使用。 ``` ca9 supply-chain report for .../demo/supply_chain/repo Packages: 4 | Edges: 3 | Findings: 3 | Block: 3 | Warn: 0 Artifact scans: 3 | Skipped artifacts: 0 Findings: [BLOCK] dependency_confusion critical acme-internal@1.0.0 Possible dependency confusion for acme-internal [BLOCK] python-startup-pth-exec critical startup-hook@1.0.0 Python startup file executes suspicious code in startup-hook [BLOCK] denied_license high license-risk@1.0.0 Denied license for license-risk ``` ### 添加动态分析以获得更好结果 ``` coverage run --source=.,$(python -c "import site; print(site.getsitepackages()[0])") -m pytest coverage json -o coverage.json ca9 scan --repo . --coverage coverage.json ``` ### 分析现有的 SCA 报告 ``` ca9 check snyk.json --repo . --coverage coverage.json ca9 check dependabot.json --repo . ``` 自动检测格式。支持 **Snyk**、**Dependabot**、**Trivy** 和 **pip-audit**: ``` ca9 check snyk.json --repo . ca9 check dependabot.json --repo . ca9 check trivy.json --repo . ca9 check pip-audit.json --repo . ``` ## 判定结果 | 判定 | 含义 | 如何操作 | |---------|---------------|------------| | `REACHABLE` | 证据表明有漏洞的包、组件或已知 API 是可达的 | **修复此漏洞** | | `UNREACHABLE (static)` | 包从未被导入 — 甚至未被间接导入 | 可以放心地抑制 | | `UNREACHABLE (dynamic)` | 包被导入了,但漏洞代码从未被执行 | 可能安全 — 保持监控 | | `INCONCLUSIVE` | 已导入但无覆盖率数据证明其被执行过 | 添加测试覆盖或手动审查 | ## 证据和置信度 每个判定都有结构化证据支持。使用 `--show-confidence` 在表格输出中查看分数,或检查 JSON/SARIF 输出中的 `evidence` 对象。 | 信号 | 检查内容 | |--------|----------------| | `advisory` | 公告来源、生态系统、别名、CWE/CPE ID,以及可用的缓存新鲜度元数据。 | | `version_in_range` | 安装的版本是否在受影响范围内(PEP 440)? | | `package_imported` | 包是否在仓库中的任何地方被导入? | | `submodule_imported` | 特定的漏洞子模块是否被导入? | | `coverage_seen` | 漏洞代码是否在测试中被执行过? | | `api_call_sites_covered` | 特定的漏洞 API 调用点是否在测试中被执行过? | | `coverage_completeness_pct` | 整体测试覆盖率百分比 — 权重动态缺失信号 | | `affected_component_source` | 如何识别出有漏洞的组件(提交分析、人工整理映射、正则表达式、类扫描)? | 置信度评分是**基于判定方向的** — 支持判定的证据会提高分数,与之矛盾的证据会降低分数。一个高置信度的 UNREACHABLE 与一个高置信度的 REACHABLE 含义不同。 | 等级 | 分数 | 含义 | |--------|-------|---------| | 高 | 80-100 | 强有力的证据支持该判定 | | 中 | 60-79 | 中等证据,具有合理的确定性 | | 低 | 40-59 | 证据薄弱,需谨慎对待 | | 弱 | 0-39 | 证据很少,建议手动审查 | ## CLI 参考 ``` ca9 scan [OPTIONS] Scan repository dependency versions via OSV.dev ca9 check SCA_REPORT [OPTIONS] Analyze a Snyk/Dependabot/Trivy/pip-audit report ca9 inventory [PATH] [OPTIONS] Show normalized package inventory ca9 vet [PATH] [OPTIONS] Run package supply-chain risk checks Common options: -r, --repo PATH Path to the project repository [default: .] -c, --coverage PATH Path to coverage.json for dynamic analysis -f, --format [table|json|sarif|vex|remediation|action-plan|markdown|html] Output format [default: table] -o, --output PATH Write output to file instead of stdout -v, --verbose Show reasoning trace for each verdict --no-auto-coverage Disable automatic coverage discovery --show-confidence Show confidence score in table output --show-evidence-source Show evidence extraction source in table output --proof-standard [strict|balanced] Proof policy for suppressions --capabilities Attach AI capability blast radius --runtime-context PATH Deployment-aware severity adjustment --trace-paths Trace exploit paths --threat-intel Enrich with EPSS and CISA KEV data --otel-traces PATH Production runtime evidence from OTLP JSON --accepted-risks PATH Accepted-risk TOML/JSON file --baseline PATH Previous ca9 JSON report for new-only gating --new-only Only gate on new reachable/inconclusive findings Scan-only options: --offline Use only cached OSV data, no network requests --refresh-cache Clear OSV cache before fetching --allow-env-fallback Use installed package versions when repo versions cannot be resolved --max-osv-workers N Max concurrent OSV detail fetches [default: 8] Inventory-only options: -f, --format [table|json] Output format [default: table] Vet-only options: --trusted-index URL Trusted package index; repeatable --private-index URL Private index allowed for internal packages --internal-package PATTERN Internal package glob, e.g. acme-*; repeatable --malware-query Query OSV for known malicious packages --scan-artifacts Hash-verify, unpack, and statically inspect artifacts --scan-workflows Scan GitHub Actions workflow risk patterns --allow-unhashed-downloads Allow artifact downloads without lockfile hashes --max-artifact-mb N Max artifact download size [default: 100] --deny-license ID Denied license identifier; repeatable --require-known-license Warn when artifact metadata has no known license --offline Use cached OSV data only for malware query Exit codes: 0 Clean — no reachable CVEs 1 Reachable CVEs found — action needed 2 Inconclusive only — need more coverage data ``` ### 配置文件 在项目根目录创建 `.ca9.toml` 来设置默认值: ``` repo = "src" coverage = "coverage.json" format = "json" verbose = true accepted_risks = "accepted-risks.toml" baseline = "ca9-baseline.json" new_only = true ``` 配置文件会从当前目录向上自动发现。CLI 标志会覆盖配置文件的值。 接受风险和基线选项使被忽略的发现项在报告输出中可见,但将其排除在退出码决策之外。 ### 缓存和离线模式 ca9 会缓存 OSV 漏洞详情(`~/.cache/ca9/osv/`,24 小时 TTL)和 GitHub 提交文件列表(`~/.cache/ca9/commits/`,7 天 TTL)以减少 API 调用。 ``` ca9 scan --repo . --offline # use cached data only, no network ca9 scan --repo . --refresh-cache # clear cache and re-fetch ``` 设置 `GITHUB_TOKEN` 以避免 ca9 为受影响组件分析获取提交数据时遇到 GitHub API 速率限制: ``` export GITHUB_TOKEN=ghp_... ca9 check snyk.json --repo . ``` ### fyn 集成 ca9 不要求安装 fyn。如果仓库有 `fyn.lock` 或 npm `package-lock.json`,`ca9 inventory` 会原生解析它,并将其视为高保真的包证据。这为 ca9 提供了精确的解析版本、直接/传递边、分组、制品哈希或完整性值、制品 URL 和源注册信息,而无需调用包管理器 CLI。 `ca9 vet` 基于该证据进行本地供应链检查。例如,一个从不受信任的索引解析的直接依赖被视为有风险,而一个匹配 `--internal-package` 模式的内部包,如果它在配置的 `--private-index` 值之外解析,则会被阻断。 当启用 `--scan-artifacts` 时,fyn 的制品 URL 和哈希值让 ca9 可以在应用恶意包启发式检查之前验证和检查已解析的 wheel/sdist。此路径不会安装包或执行包代码。相同的制品元数据通过 `--deny-license` 和 `--require-known-license` 来支持许可证检查。 未来的 ca9 命令可以将 fyn 用作依赖路径和锁文件差异上下文的可选提供者,但 fyn 的缺失不应破坏扫描或 CI 门控。 ## MCP 服务器 ca9 附带了一个 MCP 服务器,因此 LLM 驱动的工具(Claude Code、Cursor 等)可以直接运行可达性分析。 ``` pip install ca9[mcp] ``` 添加到你的 MCP 客户端配置中: ``` { "mcpServers": { "ca9": { "command": "ca9-mcp" } } } ``` 可用工具: | 工具 | 功能 | |------|-------------| | `check_reachability` | 分析 SCA 报告(Snyk、Dependabot、Trivy、pip-audit) | | `scan_dependencies` | 通过 OSV.dev 扫描仓库依赖版本 | | `check_coverage_quality` | 评估你的覆盖率数据有多可靠 | | `explain_verdict` | 深入分析特定 CVE 的判定及完整证据 | | `generate_vex` | 生成 OpenVEX 可利用性声明 | | `generate_remediation_plan` | 生成优先级排序的修复方案 | | `scan_capabilities` | 扫描 AI 能力并生成 AI-BOM | | `check_blast_radius` | 将能力爆炸半径附加到可达的 CVE | | `trace_exploit_path` | 跟踪通往漏洞 API 调用点的路径 | | `lookup_threat_intel` | 查询 EPSS 和 CISA KEV 数据 | | `enrich_sbom` | 丰富 CycloneDX 或 SPDX SBOM JSON | ## 库用法 ``` import json from pathlib import Path from ca9.parsers.snyk import SnykParser from ca9.engine import analyze data = json.loads(Path("snyk.json").read_text()) vulns = SnykParser().parse(data) report = analyze( vulnerabilities=vulns, repo_path=Path("./my-project"), coverage_path=Path("coverage.json"), ) for result in report.results: print(f"{result.vulnerability.id}: {result.verdict.value} (confidence: {result.confidence_score})") print(f" reason: {result.reason}") if result.evidence: print(f" source: {result.evidence.affected_component_source}") ``` ## 零重型依赖 ca9 的核心库依赖 `packaging` 用于 PEP 440/版本规范化,并在 Python 3.11+ 上使用 Python 标准库解析 TOML。在 Python 3.10 上,使用 `tomli` 进行锁文件解析。`click` 包是可选的 — 仅在需要使用 CLI 时才需要。 这意味着你可以将 ca9 嵌入 CI 流水线、安全工具链或其他 Python 工具中,而无需引入庞大的依赖树。 ## 局限性 - 静态分析跟踪 `import` 语句和 `importlib.metadata` 依赖树。不检测动态导入(`importlib.import_module`、`__import__`)。 - 覆盖率质量直接影响动态分析。如果你的测试没有覆盖某个代码路径,ca9 就无法动态检测到它。 - 传递依赖解析要求包已安装。如果没有安装依赖,ca9 会回退到仅检查直接导入。 - `fyn.lock` 和 npm `package-lock.json` 支持目前主要用于清单和首次 `ca9 vet` 本地供应链检查。可选的制品静态分析目前仅扫描 Python wheel/sdist 制品。完整的攻击检测仍需要更丰富的外部情报,用于维护者变更、发布年龄异常、名称仿冒、来源证明和活跃恶意软件分析。 - 目前仅支持 Python 可达性;npm 支持是清单证据,而非 JavaScript 可达性。 ## 开发 ``` git clone https://github.com/duriantaco/ca9.git cd ca9 python3 -m venv .venv && source .venv/bin/activate pip install -e ".[dev]" pytest tests/ -v ``` ## 许可证 [MPL-2.0](LICENSE)
标签:CVE分析, GPT, 代码可达性, 依赖审计, 依赖风险, 包安全, 安全分诊, 安全扫描优化, 本地引擎, 漏洞优先级排序, 漏洞管理, 软件组合分析, 逆向工具