SMC17/sentinel-sbom
GitHub: SMC17/sentinel-sbom
将 Nix flake.lock 转换为 SPDX 2.3 SBOM 的轻量级单二进制工具,具备字节级确定性输出和树内 NAR 哈希校验能力。
Stars: 1 | Forks: 0
# sentinel-sbom
[](https://github.com/SMC17/sentinel-sbom/actions/workflows/ci.yml) [](https://github.com/SMC17/sentinel-sbom/releases) [](LICENSE)
[](LICENSE)
[](https://ziglang.org/)
[](https://spdx.dev/)
[Sovereign Stack](https://stax.dev/sovereign-stack) 的一部分。作为
[`sovereign-edge`](https://github.com/SMC17/sovereign-edge) 和
[`sovereign-offense-harness`](https://github.com/SMC17/sovereign-offense-harness) 的配套工具。
## 为什么做这个
每一个 Nix `flake.lock` 已经是一个内容寻址的物料清单 (BOM) —
每个输入都有一个 git rev,一个 narHash,和一个上游 URL。将其转换为
SPDX 主要是一个序列化问题。
Anchore 的 [Syft](https://github.com/anchore/syft) 拥有一个 Nix cataloger 并且
也能生成 SPDX — Syft 功能更丰富,支持多种格式,并且被广泛使用。
这个工具的不同之处在于:
- **单一的 Zig 二进制文件,无需 Go runtime。** 更容易审计,没有传递的
Go 模块供应链。约 1,446 行代码 (LOC)。
- **字节级确定性输出**:相同的 `flake.lock` → 跨运行产生完全相同的字节
(通过 CI 中的 `sha256sum` 验证)。当您需要在不同构建之间保持 SBOM 的哈希一致性
以满足合规性或可重复性证明时,这非常有用。
- **狭窄且专注的范围**:输入 Nix flake.lock,输出 SPDX 2.3。没有
漏洞扫描(请使用 Grype),除了为最常见的 Nix flakes 内置的 30 条记录表外,没有
传递性的许可证解析。
如果您已经在使用 Syft 并且它运行良好,请继续使用 Syft。如果您想要
一个体积极小、专做一件事且能与您的 Nix flake 协同工作的二进制文件,
这就是为您准备的。
## 演示
```
$ sentinel-sbom emit --name nix-config /home/stax/.config/nix-config/flake.lock | head -8
SPDXVersion: SPDX-2.3
DataLicense: CC0-1.0
SPDXID: SPDXRef-DOCUMENT
DocumentName: nix-config
DocumentNamespace: https://stax.dev/sbom/nix-config-1778036283
Creator: Tool: sentinel-sbom-0.5.1
Created: 2026-05-06T02:58:03Z
$ sentinel-sbom emit --format=json --name nix-config /home/stax/.config/nix-config/flake.lock | jq '.packages[0]'
{
"name": "home-manager",
"SPDXID": "SPDXRef-home-manager",
"versionInfo": "00ed86e58bb6979a7921859fd1615d19382eac5c",
"downloadLocation": "https://github.com/nix-community/home-manager/archive/00ed86e58bb6979a7921859fd1615d19382eac5c.tar.gz",
"filesAnalyzed": false,
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"comment": "narHash sha256-nUoQtf4Zq7DRYJrfv904hjrxjAlWVP6a1pNNFKx3FCg=",
"checksums": [
{
"algorithm": "SHA256",
"checksumValue": "9d4a10b5fe19abb0d1609adfbfdd38863af18c095654fe9ad6934d14ac771428"
}
]
}
$ sentinel-sbom verify --strict /home/stax/.config/nix-config
OK home-manager narHash matches
OK nixpkgs narHash matches
2 ok, 0 mismatch, 0 missing
```
确定性属性:相同的 `flake.lock` → 跨运行产生完全相同的字节:
```
$ sentinel-sbom emit flake.lock | sha256sum
554eee32cb295b77b3a437de130e4300dae9f849cf1e7894a665189c67aed4e9 -
$ sentinel-sbom emit flake.lock | sha256sum
554eee32cb295b77b3a437de130e4300dae9f849cf1e7894a665189c67aed4e9 -
```
## 诚实的对比
这是一个狭窄且准确的版本。这些工具都不是直接的
竞争对手 — 它们都处理供应链堆栈的不同部分。
| | sentinel-sbom | Syft (Anchore) | Grype | Snyk |
|---|---|---|---|---|
| 功能 | flake.lock → SPDX | 多种来源 → SPDX/CycloneDX | 针对 SBOM 的漏洞扫描 | 漏洞扫描 + SCA + IaC + 容器 |
| Nix flake.lock 支持 | 原生 | 是,通过 `nix-store` cataloger | 间接 (消费 SBOM) | 有限 |
| 语言 | Zig | Go | Go | Node + 云服务 |
| 输出格式 | SPDX 2.3 (tag-value, JSON-LD) | SPDX, CycloneDX, syft JSON, table | text, JSON, SARIF, CycloneDX | 专有格式 + SARIF |
| 字节级确定性输出 | 是(通过 sha256sum 验证) | 未经正式保证;大体稳定 | 不适用 (输出随数据库变化) | 不适用 |
| narHash 验证 | 是,通过外部调用 `nix path-info`(`--strict` 模式) | 非原生 | 非该工具职责 | 非原生 |
| 定价 | 免费,AGPL | 免费,Apache | 免费,Apache | 免费层 + 付费 |
| 成熟度 | v0.5.1,单一作者 | Anchore 生产环境 + 大量用户 | 生产环境,广泛部署 | 生产环境,大型公司 |
**何时选择 `sentinel-sbom`**(高置信度):您是一名 Nix 用户,您
想要一个可以在一个下午审计完的微小单二进制工具,您关心
字节级稳定的输出,并且不需要漏洞扫描。
**何时选择 Syft**(同样有效):您想要更广泛的格式/来源
矩阵,更大的贡献者基础,并且您对您的供应链故事中包含 Go 工具链
感到满意。
**何时选择 Snyk 或商业工具**:您想要一个带有
漏洞数据和开发者工作流集成的托管产品,并且 SaaS 形态
是可以接受的。
## 状态 — 已验证与未验证的内容
`v0.5.1` — 单一作者项目,约 4 天的集中工作。
已进行端到端验证的内容:
- `zig build` + `zig build test` 均为绿色通过;**36 个单元及属性测试通过**。
- 对 6 个真实的 flake.locks 进行了冒烟测试(在此开发工作站上)。
- 两种格式的输出在跨运行时均是字节级稳定的(两次调用的输出
`sha256sum` 结果完全一致,每次都是如此)。
- `--strict` 验证运行 `nix flake archive` + `nix path-info`,并将
每个输入的 narHash 与锁文件中的 narHash 进行比较;已在此工作站的 nix-config flake 上测试。
- **`--strict --in-tree`** 运行树内 NAR 编码器 + sha256,而
无需为了计算哈希而外部调用 `nix path-info`。它仍然使用
`nix flake archive` 来发现存储路径。计算出的哈希已
在一个真实的约 200 MB nixpkgs 源码树(产生相同的 base32-SRI 哈希)上被验证为与 `nix path-info` 的 `narHash` 字段在字节级完全正确。
- **NAR 编码器属性测试**(新增,post-v0.5.1):在一个黄金语料库(空
文件,单字节,嵌入零的二进制大对象,可执行文件,双文件目录,
3 层深度嵌套树,符号链接)上与 `nix-store --dump` 进行位精确的交叉验证;分配器独立性(DebugAllocator
对比 Arena 对比 FixedBufferAllocator 产生相同的字节);对抗性
鲁棒性(在遇到缺失/不可读路径时不会发生 panic)。当 `nix-store` 不在 `PATH` 中时
跳过并按通过处理。
- **SPDX 2.3 许可证表达式处理**(新增,post-v0.5.1):复合
表达式(`AND`,`OR`,`WITH`,括号,`LicenseRef-*`,`+`
后缀)根据 SPDX 2.3 规范进行解析,并原样
往返传送至 `licenseConcluded`。
未验证的内容:
- 尚无模糊测试(已添加属性测试,模糊测试仍待完成)。
- 除了作者之外,没有实际的生产环境用户。**您将是第一个。**
- 许可证表方法(30 条记录)在锁条目不包含 SPDX 表达式时
仍然是回退方案。既没有表行也没有表达式的输入将
回退到 `NOASSERTION`。v0.6 计划引入 LICENSE-file 扫描
以填补剩余的空白。
- 完整的 SPDX 许可证列表 ID 验证(700 多个规范 ID)尚未
实现:表达式解析器接受任何符合 license-id
词法形状的标记。结构性验证(运算符,括号,WITH-RHS)
已强制执行。
## 安装
```
git clone https://github.com/SMC17/sentinel-sbom
cd sentinel-sbom
zig build # produces ./zig-out/bin/sentinel-sbom
```
或通过 Nix:
```
nix run github:SMC17/sentinel-sbom -- emit /path/to/flake.lock
```
## 用法
```
sentinel-sbom emit # SPDX 2.3 tag-value
sentinel-sbom emit --format=json # SPDX 2.3 JSON-LD
sentinel-sbom emit --name # override DocumentName
cat flake.lock | sentinel-sbom emit - # stdin
sentinel-sbom verify # rev-substring scan
sentinel-sbom verify --strict # real narHash compare
```
## SPDX 许可证表达式
`sentinel-sbom` 端到端保留 SPDX 2.3 复合许可证表达式。
当锁条目带有 `license`(或 `licenseExpression`)字段时,该值将根据 SPDX 2.3 语法([规范](https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions/))进行验证,
如果有效,将原样输出到 `licenseConcluded` 中。支持的
形式:
| 形式 | 示例 |
|--------------------------------------------------------|----------------------------------------------------------|
| 简单 ID | `MIT`, `Apache-2.0`, `GPL-3.0-only` |
| 双重 / 析取型 | `MIT OR Apache-2.0` |
| 合取型 | `MIT AND Apache-2.0` |
| 带例外 | `GPL-3.0-only WITH Classpath-exception-2.0` |
| 带括号的复合型 | `(MIT OR Apache-2.0) AND CC0-1.0` |
| `LicenseRef-*` (自定义) | `LicenseRef-Acme-Proprietary-2024` |
| `+` "或更高版本" 后缀 | `LGPL-2.1+`, `MIT+` |
无法解析的许可证字符串会在 stderr 上产生结构化警告
(`warning: sentinel-sbom: package=… field=license value=… reason=invalid-spdx-expression error=…`),
同时 `licenseConcluded` 会回退到 `NOASSERTION`,而 `licenseDeclared`
将在存在内置表值时保留该表值。
## 路线图
- **v0.5** ✅ 已发布 — 树内 NAR 编码器 + sha256,`--strict --in-tree`
哈希模式。存储路径发现仍使用 `nix flake archive`。
- **v0.6** — 针对不在内置许可证表中的输入进行 LICENSE-file 扫描;CycloneDX 输出(`--format=cyclonedx`);可重复性
证明(构建两次,进行哈希比较)。
- **v1.0** — 验证跨符号链接/可执行文件 NAR 边缘情况的 Air-gapped 运行
(目前在常规文件 + 目录输入上已验证);针对嵌套输入的递归 flake.lock 遍历。
详细计划见 [STATUS.md](STATUS.md)。
## 为什么选择 AGPL(及其实际影响)
选择 AGPL-3.0-or-later 是为了确保任何将其封装在商业服务中的人
都必须发布他们的修改。实际影响:许多
公司的法务部门直接拒绝 AGPL。如果您的公司是其中之一,这个工具对您来说不可采用,这是一个经过深思熟虑的
权衡 — 上游保持开源,集成商封装上游的
姿态与更广泛的 Sovereign Stack 论点保持一致。如果您
出于采用原因需要 MIT/Apache,[Syft](https://github.com/anchore/syft)
是基于 Apache 许可的,也是这种情况下的正确选择。
## 许可证
AGPL-3.0-or-later。参见 [LICENSE](LICENSE)。
如果您将其封装在商业产品中,您的修改必须发布到
上游 — 这是源自 Red Hat / Linux 的集成商封装上游模型。
上游保持主权。
## Sovereign Stack 的一部分
这是更广泛的“不要租用,自己运行”基础设施集合的一部分。
- [**sovereign-edge**](https://github.com/SMC17/sovereign-edge) — 作为可组合 NixOS 模块的 Open Cloudflare 级别边缘软件包
- [**sovereign-offense-harness**](https://github.com/SMC17/sovereign-offense-harness) — 带有签名审计信封的对手模拟运行器
完整产品组合请参见 [github.com/SMC17](https://github.com/SMC17)。
标签:CycloneDX替代, DevSecOps, Flake, narHash验证, Nix, NixOS, SBOM生成器, Sovereign Stack, SPDX 2.3, Zig语言, 上游代理, 单文件二进制, 可复现构建, 哈希校验, 开源合规, 确定性构建, 跌倒检测, 软件物料清单, 轻量级工具