snir-spira37/spira-trust
GitHub: snir-spira37/spira-trust
SPIRA Trust 是一个离线运行的 Python wheel 制品本地证据门禁工具,通过验证制品完整性和应用锁定策略来补充现有供应链安全工具链。
Stars: 1 | Forks: 0
# SPIRA Trust CLI
SPIRA Trust 是一个针对 Python wheel 制品的本地证据门禁。
它会在安装前验证 wheel 结构和 `RECORD` 的完整性,构建基于证据的本地依赖图和 BOM,应用锁定的本地策略,检测与已批准基线的偏移,并为企业安全工作流导出证据。
它通过证明磁盘上物理存在的内容,来补充 CVE 扫描器、包防火墙、SBOM 工具和来源系统。
`spira-trust` 是公开试点 CLI。`spira-trust trust` 对一个 Python 制品进行分类。`spira-trust graph` 将相同的证据模型扩展到你明确提供的本地 wheel 文件夹。
公开的 wheel 仅暴露 `spira-trust` 和 `spira`。内部的 SPIRA 工作区命令不属于公开试点 wheel 的一部分。
它提出一个狭窄的问题:
```
What does this package claim, what does the raw artifact prove, and is there
enough evidence to return TRUST_OK_WITH_NOTES, TRUST_WARN, or TRUST_BLOCK?
```
有关市场定位和能力边界,请参阅
[`docs/capability_matrix.md`](https://github.com/snir-spira37/spira-trust/blob/main/docs/capability_matrix.md)。
## 安装
从生产环境 PyPI 安装:
```
python -m pip install spira-trust==0.5.8
spira-trust version
```
每个生产版本都通过 Trusted Publishing/OIDC 发布到 PyPI。
PyPI 会显示 wheel 制品的发布来源。
有关发布关闭说明,请参阅
[`docs/030_closure_note.md`](https://github.com/snir-spira37/spira-trust/blob/main/docs/030_closure_note.md)。
如需试点接入,请从
[`docs/pilot_readiness_pack.md`](https://github.com/snir-spira37/spira-trust/blob/main/docs/pilot_readiness_pack.md) 开始。
有关 CI,请参阅 [`docs/ci_quickstart.md`](https://github.com/snir-spira37/spira-trust/blob/main/docs/ci_quickstart.md)。
有关外部试点交接,请参阅:
- [`docs/pilot_outreach_email.md`](https://github.com/snir-spira37/spira-trust/blob/main/docs/pilot_outreach_email.md)
- [`docs/why_spira_trust.md`](https://github.com/snir-spira37/spira-trust/blob/main/docs/why_spira_trust.md)
- [`docs/demo_10_minutes.md`](https://github.com/snir-spira37/spira-trust/blob/main/docs/demo_10_minutes.md)
- [`docs/ci_pilot_15_minutes.md`](https://github.com/snir-spira37/spira-trust/blob/main/docs/ci_pilot_15_minutes.md)
- [`docs/pilot_feedback_questions.md`](https://github.com/snir-spira37/spira-trust/blob/main/docs/pilot_feedback_questions.md)
用例:
- [`docs/ai_assisted_builds.md`](https://github.com/snir-spira37/spira-trust/blob/main/docs/ai_assisted_builds.md) - 在安装或发布之前,使用 SPIRA Trust 对由编码代理生成的 Python wheel 制品进行门禁检查。
从本仓库安装:
```
python -m pip install .
```
## 运行
默认输出人类可读的内容:
```
spira-trust trust path/to/package.whl --output-dir spira_trust_out
```
机器可读的输出:
```
spira-trust trust path/to/package.whl --output-dir spira_trust_out --format json
```
该命令会写入:
- `artifact_trust_summary.txt`
- `artifact_trust_report.json`
- 所选输出目录下的详细本地证据文件
默认情况下,CLI 仅打印摘要和公开报告路径。当你希望在终端中打印详细的本地证据路径时,请使用 `--full-evidence`。
## 本地信任图
当你拥有多个本地 wheel 并希望在其声明的关系之上构建一个证据图时,请使用 graph 模式:
```
spira-trust graph path/to/wheel-folder --output-dir spira_graph_out
```
你可以传入单个 wheel 或文件夹。文件夹仅会扫描 `*.whl` 文件:
```
spira-trust graph dist/pkg_a.whl dist/pkg_b.whl --output-dir spira_graph_out
```
机器可读的输出:
```
spira-trust graph path/to/wheel-folder --output-dir spira_graph_out --format json
```
### 基于证据的 CycloneDX 导出
SPIRA Trust 可以从其本地证据 BOM 导出 CycloneDX JSON SBOM:
```
spira-trust graph path/to/wheel-folder --output-dir spira_graph_out --sbom cyclonedx-json
```
默认输出路径为:
```
spira_graph_out/spira-trust.cdx.json
```
此 SBOM 仅根据提供的本地 wheel 制品生成。它包含制品 SHA-256 哈希、RECORD 验证状态、所提供 wheel 之间的本地依赖关系、SPIRA 图状态、策略状态以及证据属性。顶级属性同时包含源图判定 (`spira:graph:verdict`) 和组合策略判定 (`spira:policy:combined_verdict`)。
未作为本地 wheel 提供的已声明依赖项会被记录为 SPIRA 属性 (`spira:declared_missing_requirement`),而不会被捏造为 CycloneDX 组件。
在 wheel 的 `.dist-info/sboms/` 目录下发现的嵌入式 SBOM 文件会作为证据属性记录,包含路径、SHA-256、字节数和格式提示。
默认情况下,SPIRA 不会信任、解析、合并、验证或修改嵌入式 SBOM 文件。当提供 `--verify-embedded-sboms` 时,SPIRA 会针对本地 wheel 元数据,对支持的 JSON/CycloneDX 元数据字段执行狭窄的本地一致性检查。这不是完整的 SBOM schema 验证。
默认情况下会省略绝对本地路径。仅在需要用于本地/私有工作流时才包含它们:
```
spira-trust graph path/to/wheel-folder --output-dir spira_graph_out --sbom cyclonedx-json --include-local-paths
```
该导出不会添加漏洞情报、恶意软件检测、法律许可分析、来源验证、依赖解析、网络访问、wheel 修改或代码执行。
如果你希望图证据记录生成该运行的 bundle/build SHA,请显式传入:
```
spira-trust graph path/to/wheel-folder --output-dir spira_graph_out --bundle-sha256
```
严格关闭模式会将未提供 wheel 的已声明关系视为警告,而不是信息提示:
```
spira-trust graph path/to/wheel-folder --strict-closure
```
许可证策略筛查是可选且显式的。当你希望 SPIRA 筛查观察到的许可证可见性字符串时,请提供一个本地 JSON 策略文件:
```
spira-trust graph path/to/wheel-folder --license-policy policy.json
```
策略示例:
```
{
"schema": "SPIRA_LICENSE_POLICY_V1",
"schema_version": "1.0",
"match_mode": "case_insensitive_substring",
"blocked_terms": ["AGPL", "General Public License"],
"warn_terms": ["Custom", "Unknown"]
}
```
声明的 entry-point 命令筛查也是可选且显式的。当你希望 SPIRA 筛查由 wheel `entry_points.txt` 声明的命令名称时,请提供一个本地 JSON 策略文件:
```
spira-trust graph path/to/wheel-folder --entry-point-policy entry_policy.json
```
Entry-point 策略示例:
```
{
"schema": "SPIRA_ENTRY_POINT_POLICY_V1",
"schema_version": "1.0",
"match_mode": "exact_command_name",
"case_insensitive": true,
"blocked_command_names": ["pip", "python", "pytest"],
"warn_command_names": ["ls", "ssh"]
}
```
目标环境筛查是可选且显式的。它会读取 wheel 文件名标签,并将其与本地目标 JSON 文件进行比较:
```
spira-trust graph path/to/wheel-folder --target-environment target_environment.json
```
目标示例:
```
{
"schema": "SPIRA_TARGET_ENVIRONMENT_V1",
"schema_version": "1.0",
"python_tag": "cp312",
"abi_tag": "cp312",
"platform_tag": "manylinux_2_17_x86_64",
"strict_target": false,
"block_on_mismatch": false
}
```
Graph 模式会写入:
- `graph_summary.txt`
- `graph_report.json`
- `spira-decision.json` 和 `spira-decision.md`,用于 CI/审计集成的稳定决策契约
- `bill_of_materials.json`,包含所提供 wheel 的 BOM、声明的本地关系深度、许可证可见性以及子树完整性摘要
- `input_manifest.json`,包含每个提供的 wheel 的 SHA、规范化名称、文件数和解压后的字节数
- `graph_evidence_manifest.json`,包含 schema 版本、工具身份、环境捕获和证据文件哈希
- 用于图输出的治理 package-lock 证据
- `graph_evidence_package_result.json`,包含治理包路径和锁定后验证结果
要将决策和证据文件一起归档,请传入:
```
spira-trust graph path/to/wheel-folder --output-dir spira_graph_out --evidence-pack spira-evidence.zip
```
Graph 模式特意设计为仅限本地:
- 无依赖解析器
- 无 PyPI/网络访问
- 无安装
- 无包代码执行
- 仅包含来自本地 wheel `METADATA` 的声明关系
## 物料清单
Graph 模式会在 `bill_of_materials.json` 中写入本地 BOM。该 BOM 仅作展示之用:它记录的是所提供的本地 wheel 集中实际存在的内容,而不是可能存在于 PyPI 或解析器输出中的内容。
该 BOM 包括:
- root、provided-transitive、orphan 和 declared-missing 制品
- 仅针对所提供 wheel 子图的 `depth`
- 来自 wheel `METADATA` 的 `metadata_license` 和 `license_classifiers`
- 检测到的类似 LICENSE/COPYING/NOTICE 的文件及其路径、SHA-256 和字节数
- 针对提供的本地节点的 `subtree_integrity_digest`
- `digest_covers_fields`,写入 BOM 以明确摘要的覆盖范围
- 当提供 `--license-policy` 时的可选 `license_policy_screening`
- 声明的 `entry_points.txt` console/gui 命令名称以及单独的 `entry_points_digest`
- 当提供 `--entry-point-policy` 时的可选 `entry_point_policy_screening`
- wheel 文件名 python/abi/platform 标签以及单独的 `wheel_tags_digest`
- 当提供 `--target-environment` 时的可选 `target_environment_screening`
- `combined_policy_verdict`,一个针对已评估层的透明聚合器
`subtree_integrity_digest` 用于针对受信任的基线提供防篡改证据。它不是安全性证明,也不涵盖未提供 wheel 的声明依赖项。该摘要是根据本地内容和声明的可见性字段计算得出的,而不是根据图判定或策略模式计算。
V3-A 中的许可证数据是可见性,而非法律策略。SPIRA 将声明的元数据和许可证文件证据分开记录;它不会在 BOM 中对它们进行比较或出具许可证合规性发现。
当提供许可证策略文件时,V3-B 会对观察到的许可证可见性值执行狭窄的本地字符串筛查。这仍然不是法律建议、法律合规认证、SPDX 表达式解析或语义许可证解释。被阻止的策略匹配会使图判定变为 `GRAPH_BLOCK`;警告匹配会使图判定变为 `GRAPH_WARN`。
当提供 entry-point 策略文件时,V3-C 会执行声明的命令名筛查。这是 `DECLARED_ENTRY_POINT_NAME_SCREENING`,而不是运行时的 PATH 劫持检测。匹配是在策略的大小写敏感规则下的精确命令 token 匹配。SPIRA 不会合并可执行变体,如 `.exe`、`pip3`、模糊拼写错误或视觉易混淆字符。Entry-point 可见性和策略不会改变 `subtree_integrity_digest`;entry points 有其自己的 `entry_points_digest`。
当提供目标环境文件时,V3-D 会执行狭窄的 wheel 文件名标签相关性筛查。这不是完整的 `packaging.tags` 兼容性检查,也不是可安装性保证。诸如 `py3-none-any` 之类的通用 wheel 不会被视作目标不匹配。默认情况下,非精确的目标相关性仅会产生提示;`strict_target` 可以引发警告;`block_on_mismatch` 是显式的用户策略选择,并非安全缺陷声明。目标标签可见性和策略不会改变 `subtree_integrity_digest`;wheel 标签有其自己的 `wheel_tags_digest`。
组合判定仅是一个聚合器。它保留 `per_layer` 详情,记录 `decided_by`,并将 `evaluated_layers` 与 `not_evaluated_layers` 分开。未运行的层是 `NOT_EVALUATED`,而不是 `OK`。
`GRAPH_OK` 仅表示在已评估的各层中均为 OK。
Lockfile 交叉检查是可选且显式的。当你希望 SPIRA 将锁定的包信息与你提供的本地 wheel 进行比较时,请提供一个本地 lockfile:
```
spira-trust graph path/to/wheel-folder --lockfile requirements.txt
```
V4-B/V5 支持一个狭窄的本地子集:`name==version` 需求行、pip-tools 风格的多行 `--hash=sha256:` 需求、`Pipfile.lock` JSON 包名/版本/哈希字段、`poetry.lock` `[[package]]` 名称/版本/哈希信息,以及 `uv.lock` `[[package]]` 名称/版本/哈希信息。
不支持的语法会被报告为提示,既不会被静默接受,也不会仅因为不支持而被硬阻断。SPIRA 不会从 lockfile 解析、获取、建议或安装包。针对所提供 wheel SHA 的 lockfile 哈希不匹配是一个阻塞性的事实矛盾。
同时也支持统一策略包:
```
spira-trust graph path/to/wheel-folder --policy-pack spira-policy.json --policy-sha256
```
策略包的优先级是确定性的:环境覆盖,然后是显式 CLI 标志,接着是策略包内容,最后是默认值。如果提供 `--policy-sha256`,则策略包必须是自包含的。在图扫描之前,锁定包内部的路径引用会被以 `POLICY_UNTRUSTED` 拒绝,因为否则锁定将仅保护包装文件,而不保护所引用的策略内容。每次运行都会将 `effective_policy.json` 写入图证据包中,以便审计人员可以查看运行了哪些层以及每个值的来源。缺失的包部分状态为 `NOT_EVALUATED`,而不是 `OK`。
## 基线偏移监控
当你已经批准了一个 BOM 并想知道当前本地 wheel 文件夹是否发生了更改时,请使用 drift 模式:
```
spira-trust drift path/to/wheel-folder --baseline baseline_bill_of_materials.json --baseline-sha256
```
偏移是一个事实,而不是一个判定。更改的 wheel、添加的 wheel、移除的 wheel 或更改的 `subtree_integrity_digest` 意味着当前文件夹不再匹配你所锁定的基线。这并不意味着更改是恶意的。策略判定保持独立,并且仍然可以返回 `BLOCK` 或 `WARN`。
`--baseline-sha256` 锁定是信任锚点。如果提供了该参数但基线文件不匹配,SPIRA 将在扫描当前文件夹之前拒绝执行。
如果省略该参数,则允许运行,但报告会将基线标记为 `UNPINNED`;`NO_DRIFT` 随后仅表示 wheel 与该本地基线文件匹配。
Drift 模式写入:
- `drift_summary.txt`
- `drift_report.json`
- `current_graph/...` 包含当前图/BOM 证据
- 用于 drift 报告的可选治理 package-lock 证据
重新设定基线特意设计为非自动。在审查偏移后,设定新基线是一项人工决策。
## 重新设定基线
仅在审查了偏移并决定当前 wheel 文件夹应成为新的受信任基线后,才使用 rebaseline 模式:
```
spira-trust rebaseline path/to/wheel-folder --from-baseline old_bill_of_materials.json --baseline-sha256 --output-dir new_baseline_out --yes
```
如果没有 `--yes`,SPIRA 会写入偏移预览并以 `REBASELINE_REQUIRES_CONFIRMATION` 退出。使用 `--yes` 时,SPIRA 会将当前 BOM 复制到新的基线文件,并打印必须在 CI 中锁定的新 SHA-256。
旧的基线永远不会被修改。CI 模板绝对不能自动运行 rebaseline。
## 退出代码
```
0 no blocking trust failure was found
1 TRUST_BLOCK
2 TRUST_UNKNOWN
```
`spira-trust graph`:
```
0 GRAPH_OK or GRAPH_OK_WITH_UNVERIFIED
1 GRAPH_BLOCK
1 GRAPH_INPUT_ERROR; invalid graph input such as an empty wheel folder
2 GRAPH_WARN
```
对于 `spira-trust drift`:
```
0 NO_DRIFT
1 BLOCK from current policy evaluation
1 DRIFT_INPUT_ERROR; invalid current input such as an empty wheel folder
2 WARN from current policy evaluation
3 DRIFT_DETECTED against the pinned baseline
4 BASELINE_UNTRUSTED; baseline sha256 pin mismatch, no drift computed
```
对于策略包验证:
```
5 POLICY_UNTRUSTED; policy pack sha mismatch or pinned pack references external paths
6 REBASELINE_REQUIRES_CONFIRMATION; no new baseline written without --yes
```
对于 `spira-trust rebaseline`,无效的当前输入(例如空的 wheel 文件夹)会被报告为 `REBASELINE_INPUT_ERROR` 并以退出码 1 结束。输入错误会作为清晰的报告写入输出目录中;它们绝对不能暴露原始的 Python 堆栈跟踪。
## 判定的含义
```
TRUST_OK_WITH_NOTES
No hard gap was found in the checks SPIRA ran, but there is surfaced prose
or other context that still needs a human reading.
TRUST_WARN
One or more structured claims are present but not raw-verifiable, or the
uncertainty is high enough that manual review is required before trust.
TRUST_BLOCK
SPIRA found blocking evidence such as RECORD/package-lock integrity failure
or a condition-matched contradiction.
GRAPH_OK
All provided graph nodes passed the checks SPIRA ran and no declared local
relationship introduced a warning or block.
GRAPH_OK_WITH_UNVERIFIED
No blocking contradiction was found, but at least one declared relationship
pointed to an artifact that was not provided locally. This is not a clean
closure claim; the summary reports the number of unverified nodes.
GRAPH_WARN
The graph has non-terminal risk, such as strict-closure missing artifacts or
a warning propagated from a child package.
GRAPH_BLOCK
A provided artifact was blocked, or a blocking child propagated through a
declared/runtime relationship.
```
请参阅
[`docs/verdicts.md`](https://github.com/snir-spira37/spira-trust/blob/main/docs/verdicts.md)
获取有关判定、矛盾和常见修复方法的本地说明。
## 自动检查的内容
对于 Python wheel、zip 和文件夹,v1 会机械地检查原始格式明确的受结构化表面:
- `METADATA` 包名和版本
- `Requires-Dist`
- `Requires-Python`
- 声明但无原始证据的 `Development Status` 分类器
- 针对 archive 内容的 wheel `RECORD` 哈希
- 审查报告上的 SPIRA package-lock 和 `not_claimed` 边界
然后,信任决策会通过 SPIRA 的打包本地决策适配器进行路由,该适配器的捆绑 Python 源码在使用前已进行 SHA 验证。
Graph 模式仅使用 PEP 503 包名规范化:转为小写,并将 `-`、`_` 和 `.` 的连续序列合并为 `-`。它不执行 Unicode 易混淆折叠或模糊匹配。同形异义词和字节级路径不匹配仍然是单制品审查层的责任。
同一个包的多个本地版本会使用包名、版本和制品哈希作为不同的图节点进行保留。如果声明的依赖关系匹配到多个本地 wheel,图模式会将该关系标记为模棱两可,而不是静默地选择一个版本。声明运行时关系的模棱两可状态将作为故障关闭(fail-closed),表现为 `GRAPH_BLOCK`。
Graph 模式还会对结构性图风险采取故障关闭:
- 循环声明关系为 `GRAPH_BLOCK`
- 从同一个 root 可达的锁定版本冲突为 `GRAPH_BLOCK`
- 所提供的本地版本均不满足的支持范围约束为 `GRAPH_BLOCK`
- 结构上无法满足的支持范围约束为 `GRAPH_BLOCK`
- 选定的格式错误或无法满足的环境标记类为 `GRAPH_BLOCK`(空标记、引号不平衡、悬空的布尔运算符、无值的比较,以及明显冲突的 `python_version` 边界)
- 大于 v1 规模限制的制品集会在单制品审查开始前判定为 `GRAPH_BLOCK`
范围检查仅在本地进行。它们仅评估你提供的磁盘上的 wheel 版本;绝不会获取、建议、推断或发明其他版本。
V2 范围检查仅支持带有这些运算符的数字元组版本:`==`、`!=`、`<`、`<=`、`>`、`>=`,以及逗号交集,例如 `>=1,<2`。
兼容版本运算符 `~=` 和非数字 PEP 440 格式(例如 `rc`、`dev`、本地版本、epochs 和通配符)将变为 `UNVERIFIED` 提示。
它们不会静默通过,也不会仅仅因为 SPIRA 不解析它们而进行硬阻断。
Graph 模式不声称自己是完整的 PEP 440 或 PEP 508 验证器。
## 安全边界
- 归档解压会阻止目录遍历路径,并强制执行文件/成员/字节限制。
- v1 绝**不会**导入或执行受审查制品中的代码。
- 在存在真正的沙箱之前,v1 会禁用运行时探测。
- 自由文本将作为 `NEEDS_HUMAN_JUDGMENT` 呈现;它不会被自动分类。
## 不会执行的操作
- 它不是恶意软件扫描器。
- 它不是安全认证。
- 它不执行完整的包审计。
- 它不检查 CVE。
- 它不解析或下载依赖项。
- 它不执行包代码。
- 当包内部一致时,它不检测拼写错误抢注。
- 它不自动解释 README 或冗长的自由文本描述。
- 它不证明一个包是好的、安全的或可用于生产环境的。
## 试点问题
对于试点用户而言,有用的问题不是“SPIRA 令人印象深刻吗?”而是:
```
Did this verdict and evidence help you decide anything about the package you chose?
```
诚实的负面反馈是有效的输出。如果报告令人困惑、太嘈杂或没有用处,这正是试点旨在发现的情况。
标签:BOM, Google Gemini, Python, 依赖管理, 文档结构分析, 无后门, 组件分析, 逆向工具