Builder106/ClearHash

GitHub: Builder106/clear-hash

ClearHash 是一个用 Rust 编写的供应链完整性验证工具,通过从源码重建包并与注册表构件进行文件树哈希比对来检测供应链篡改。

Stars: 0 | Forks: 0

ClearHash — rebuild every package, compare every byte, block every tamper [![CI](https://img.shields.io/badge/CI-passing-success.svg)](.github/workflows/ci.yml) [![Rust](https://img.shields.io/badge/rust-1.88%2B-orange.svg)](https://www.rust-lang.org/) [![许可证](https://img.shields.io/badge/license-MIT-blue.svg)](#license) [![生态系统](https://img.shields.io/badge/ecosystems-npm%20%7C%20PyPI%20%7C%20Cargo-7c3aed.svg)](#ecosystem-support) [![Sigstore](https://img.shields.io/badge/SLSA-Sigstore%20%2B%20Rekor-22c55e.svg)](https://www.sigstore.dev/) [![演示](https://img.shields.io/badge/demo-clear-hash.vercel.app-success.svg)](https://clear-hash.vercel.app/) ClearHash 回答了现有供应链中几乎没有工具能够回答的一个问题: **“我即将安装的二进制文件,真的是由它所声称的源代码构建出来的吗?”** ## 它能捕获什么 过去五年中的供应链攻击(event-stream、ua-parser-js、 安装后的加密钱包窃取程序、xz-utils)都具有同一种形态:注册表 tarball 与源代码仓库不一致。现有工具验证的是*谁*签署了 tarball (Cosign),或者 *tarball 在各个镜像之间是否匹配* (Sigstore transparency),而不是*tarball 是否与源代码所产生的内容一致*。 ClearHash 真正执行了重新构建和比较。 ``` $ clearhash verify npm:sigstore@2.3.1 [1/5] Fetching sigstore from npm sha256: 1b5041a35f86125db7f872742502470753fd2e1109521b7dbff8a61d229a03c2 [2/5] Verifying Sigstore attestation commit: 46e7056ff991 (workflow: https://github.com/sigstore/sigstore-js/.github/workflows/release.yml@refs/heads/main) [3/5] Spinning up rebuild container (node:20.11.1-bookworm-slim) [4/5] Rebuilding from source at commit 46e7056ff991 built: /tmp/clearhash-rebuild-CuR0xW/rebuilt/out/sigstore-2.3.1.tgz [5/5] Comparing file trees ✓ MATCH npm:sigstore@2.3.1 tree-hash ec714016d7e4ce742f9aa23b6f16f19cb967bf82b78c343297013dcc268b107e ``` 被篡改的构件会得到不同的结果。要查看 ClearHash 实际能捕获什么,请使用 `--simulate-tamper` 运行验证,该命令会在比较之前故意修改从注册表提取的文件树(来自 npm 的底层 tarball 是干净的——模拟过程会被明确提示): ``` $ clearhash verify --simulate-tamper npm:sigstore@2.3.1 ⚠ TAMPER SIMULATION · The registry-extracted tree will be modified before comparison (All mode). The MISMATCH below is real but the registry tarball is clean. [1/5] Fetching sigstore from npm [2/5] Verifying Sigstore attestation [3/5] Spinning up rebuild container (node:20.11.1-bookworm-slim) [4/5] Rebuilding from source at commit 46e7056ff991 [5/5] Comparing file trees · tampered injectedpayload: dist/.clearhash-tamper-demo.js (added a file that the source rebuild did not produce) · tampered contentswap: dist/index.js (appended bytes to an existing file) · tampered modeflip: dist/index.js (flipped the executable bit) · tampered deletion: README.md (removed a file) ✗ MISMATCH npm:sigstore@2.3.1 — 4 difference(s) OnlyInRegistry { path: "dist/.clearhash-tamper-demo.js" } ContentDiffers { path: "dist/index.js" } ModeDiffers { path: "dist/index.js" } OnlyInRebuild { path: "README.md" } ``` 四种篡改模式(`injected-payload`、`content-swap`、`mode-flip`、`deletion` 或 `all`)各自会呈现出不同的差异类别。这对于演示、截图以及针对差异渲染路径编写集成测试非常有用,且无需真正的恶意包。 ### 在线演示
clearhash verify npm:sigstore@2.3.1 — 完整流水线(约 36 秒,4 倍速播放) ![验证演示](https://static.pigsec.cn/wp-content/uploads/repos/cas/d6/d6b9ab45a8a09a13382e38f700d147e1d14440c7630579dd80a6431028e78848.gif)
clearhash inspect npm:sigstore@2.3.1 — 证明摘要,无重新构建 ![检查演示](https://static.pigsec.cn/wp-content/uploads/repos/cas/5f/5f7c37ccc2cf5b5579d97e979f7bfb689e91b6044bca20f20bad34fbe7b2a97e.gif) ## 工作原理 ``` sequenceDiagram participant CLI as clearhash CLI participant Reg as Registry (npm / PyPI / cratesio) participant Sig as Sigstore + Rekor participant Git as Source Git Repo participant Docker CLI->>Reg: fetch artifact (.tgz) CLI->>Reg: fetch SLSA attestation bundle CLI->>Sig: verify cert chain (Fulcio) Sig-->>CLI: workflow identity URI + Rekor log index Note over CLI: cross-check: workflow URI's
repo == attested source repo CLI->>Git: git clone + checkout attested commit Note over CLI: verify HEAD == attested commit CLI->>Docker: pull pinned rebuild image CLI->>Docker: run ecosystem build script (npm pack / pip build / cargo package) Docker-->>CLI: rebuilt artifact (.tgz / sdist / .crate) CLI->>CLI: extract both archives, normalize (strip mtimes, scrub registry metadata) CLI->>CLI: Merkle-hash both trees alt hashes match CLI-->>User: ✓ MATCH (exit 0) else hashes differ CLI-->>User: ✗ MISMATCH + per-file diff (exit 1) end ``` 比较模型是**文件树内容哈希**,而不是字节完全相同的 tarball SHA-256。 如今,即使使用 `SOURCE_DATE_EPOCH`,也无法实现严格的字节相等——在多次 `npm pack` 调用之间,npm tarball 的顺序、gzip 压缩级别以及注册表注入的 `package.json` 元数据都会有所不同。ClearHash 会对双方进行标准化(剥离 mtimes、标准化模式、丢弃四个 npm 注入的字段 `_id`、`_integrity`、`_resolved`、`dist`),并对生成的文件树比较 Merkle 根。 ## 免安装体验 **inspect** endpoint 的托管实例运行在 [**clear-hash.vercel.app**](https://clear-hash.vercel.app)。它负责执行流水线中的获取 + Sigstore 解析 + 证书链验证部分。完整的 **verify** 流程仍保留在 CLI 中, 因为它需要 Docker daemon。 ``` curl 'https://clear-hash.vercel.app/api/inspect?package=npm:sigstore@2.3.1' ``` 或者将包名粘贴到 [`/inspect`](https://clear-hash.vercel.app/inspect) 的表单中。 ## 安装 CLI 需要 Rust 1.88+ 以及一个正在运行的 Docker daemon(macOS 上可使用 Docker Desktop 或 OrbStack)。 ``` git clone https://github.com/Builder106/ClearHash.git cd ClearHash cargo install --path crates/clearhash-cli clearhash --version ``` ## 使用 ``` # 完整 pipeline:fetch + attest + rebuild + compare clearhash verify npm:sigstore@2.3.1 # 仅 fetch + parse attestation envelope(无需 docker) clearhash inspect npm:sigstore@2.3.1 # 用于 CI 的 JSON 输出 clearhash verify npm:sigstore@2.3.1 --json # Cargo 目前还没有实际的 SLSA attestation —— 需明确 opt in clearhash verify --allow-unattested cargo:serde@1.0.197 # 保留 workdir 以 inspect 不匹配的情况 clearhash verify npm:foo@1.0.0 --keep-workdir # Demo:故意篡改提取的 registry tree,使 diff 不为空。 # (输出已明确标记为 simulation;npm tarball 是干净的。) clearhash verify --simulate-tamper npm:sigstore@2.3.1 clearhash verify --simulate-tamper=content-swap npm:sigstore@2.3.1 ``` ### 退出码 | 代码 | 含义 | |------|---------| | 0 | 文件树哈希匹配。可以安全安装。 | | 1 | 文件树哈希不匹配*或*证明签名无效。应阻止安装。 | | 2 | 无 SLSA 证明,且未传递 `--allow-unattested`。 | | 3 | 基础设施故障(无 docker、无网络等)。 | ## 生态系统支持 | 生态系统 | 状态 | 证明来源 | 重新构建镜像 | |---|---|---|---| | **npm** | ✅ 端到端 | `registry.npmjs.org/-/npm/v1/attestations/...` | `node:20.11.1-bookworm-slim` | | **PyPI** | 🚧 adapter 脚手架 | PEP 740 `/integrity/.../provenance` | `python:3.12.2-slim-bookworm` | | **Cargo** | 🚧 adapter 脚手架 | _无 —— 需要使用 `--allow-unattested`_ | `rust:1.78-slim-bookworm` | npm 路径目前已实现端到端可用。PyPI 和 Cargo 将在相同的 `EcosystemAdapter` trait 之后整合其完整的重新构建流程——无需对引擎进行更改。 ## v1 验证的内容(以及推迟到 v1.1 的内容) **v1(当前版本):** - 信封结构完整性(SLSA v0.2 *和* v1 in-toto 语句) - 从 Sigstore bundle 中提取 X.509 叶子证书 - 签发者 = Fulcio(拒绝带有非 Fulcio 叶子证书的 bundle) - 主题备用名称 (Subject Alternative Name) → GitHub Actions 工作流 URI - **交叉检查**:工作流 URI 的 `owner/repo` 段与已证明的源仓库匹配 - 存在 Rekor 透明日志条目 - 源仓库 `git clone` + 提交锁定,并验证 `HEAD == attested-commit` - 网络隔离的重新构建容器(默认使用桥接模式;构建需要网络以执行 `npm ci`) - 文件树 Merkle 比较,并提供逐文件差异输出 **v1.1(下一版本):** - 使用叶子证书的公钥进行完整的 Cosign DSSE 签名验证 - 完整的 Rekor Merkle 包含证明验证 - 通过预获取的离线依赖缓存实现物理隔离的重新构建 - PyPI 的 wheel 验证(目前仅支持 sdist) ## 架构 ``` ClearHash/ ├── crates/ │ ├── clearhash-cli/ # CLI binary; clap, console, tokio │ ├── clearhash-web/ # bin + lib; shared Axum router (clearhash_web::app) │ ├── clearhash-core/ # shared types: PackageRef, ProvenanceClaim, FileTreeHash │ ├── clearhash-registry/ # reqwest fetchers │ ├── clearhash-provenance/ # Sigstore + Rekor + envelope parsing │ ├── clearhash-sandbox/ # bollard Docker orchestration + tree compare │ └── clearhash-ecosystems/ # EcosystemAdapter trait + npm/pypi/cargo impls ├── api/ │ └── clearhash.rs # Vercel function; wraps clearhash_web::app with VercelLayer ├── Cargo.toml # workspace root *and* a [package] that builds api/clearhash.rs ├── vercel.json # Vercel rewrites + runtime config ├── Dockerfile # alternative: multi-stage build of clearhash-web binary └── fly.toml # Fly.io deployment config (Docker-based) ``` 每一个特定于生态系统的特性都位于 `EcosystemAdapter` 之后。引擎 crate 仅依赖于 该 trait —— 添加一个新的生态系统只需在 `clearhash-ecosystems/src/` 下添加一个新文件。 ## 自行部署 Web 前端可以通过两种方式部署。主要目标是 **Vercel**——整个 Axum 路由器通过 [官方 Vercel Rust runtime](https://vercel.com/docs/functions/runtimes/rust) 作为单个 Rust serverless 函数运行。 次要目标是任何兼容 Dockerfile 的主机(Fly.io、Render、Railway、普通 VPS)。 ### Vercel(推荐) ``` # 一次性操作:将此 repo 链接到新的 Vercel project vercel link # Deploy vercel deploy --prod ``` Vercel 会自动检测 [vercel.json](vercel.json) 以及 [Cargo.toml](Cargo.toml) 中的根 `[package]` + `[[bin]]` 条目。 函数二进制文件由 [api/clearhash.rs](api/clearhash.rs) 构建, 它使用 `vercel_runtime::axum::VercelLayer` 包装了共享的 `clearhash_web::app` 路由器,因此每个路由都会通过为本地服务器提供支持的相同 handler 运行。 ### Docker(Fly.io / Render / Railway) ``` # Fly.io(一次性) flyctl launch --copy-config --no-deploy flyctl deploy # 或者 build + 在本地运行 docker build -t clearhash-web . docker run --rm -p 8080:8080 clearhash-web open http://localhost:8080 ``` 部署的 Docker 镜像约为 158 MB(debian:bookworm-slim + 约 5 MB 的 Rust 二进制文件 + 资产)。 无需注册表凭据、无需 DB、无需 secrets——它只是代理实时的 npm/PyPI API。 ## 注意事项和威胁模型 ClearHash 将攻击面从“注册表向你提供它想提供的任何内容”缩小为 “注册表向你提供由已证明的源代码产生、且由签署该证明的工作流所构建的构件。” 注意事项: 1. **lockfile 是信任根的一部分。** 如果 `package-lock.json` 本身在已证明提交的 HEAD 处被篡改,重新构建将忠实地重现被篡改的文件树。这是正确的:证明声明的是“这就是源代码产生的内容”——重新构建验证的是这一声明,而不是“源代码是良性的”。 2. **重新构建容器具有网络访问权限**,用于安装依赖(`npm ci`、`pip install`、`cargo download`)。`--ignore-scripts` 阻止了影响最大的数据泄露路径(生命周期钩子)。完全物理隔离的构建将在 v1.1 中实现。 3. **确定性失败的表现与篡改完全相同。** 如果 `npm pack` 对于给定的包确实是非确定性的(例如嵌入了主机名),ClearHash 会将其标记为不匹配。`--keep-workdir` 标志用于对这些情况进行分类排查。 4. **必须使用 Docker。** 没有 daemon,就没有验证。macOS 用户:需要 Docker Desktop 或 OrbStack。 ## 贡献 请参阅 [CONTRIBUTING.md](CONTRIBUTING.md)。 ## 许可证 [MIT](LICENSE)。
标签:DevSecOps, Rust, Sigstore, SLSA, 上游代理, 包管理器, 可视化界面, 文档安全, 统一API, 网络流量审计, 请求拦截, 软件完整性验证, 通知系统