matteo-sung/lockvet

GitHub: matteo-sung/lockvet

lockvet 是一款跨 20 种 lockfile 格式的依赖变更审查工具,在合并前清晰解释每次升级带来的风险与收益。

Stars: 0 | Forks: 0

# lockvet **在合并任何 lockfile 变更之前,先搞清楚它改了什么。** ![lockvet 在一次常规的 dependabot 补丁版本升级中发现隐藏的 RUSTSEC 安全公告](https://static.pigsec.cn/wp-content/uploads/repos/cas/fe/feb990a29f3179438accb63f8988f99a1e4f1ac7319276f765552da59feb36ff.gif) *真实案例:在 [sharkdp/fd](https://github.com/sharkdp/fd) 中,一次针对 `jiff` 的 dependabot “补丁”版本升级 悄无声息地添加了 7 个传递依赖 —— 其中一个被 RUSTSEC 标记。* lockfile 的 diff 简直没法看 —— 一次常规的 `npm install` 可以重写数千行代码, 而 Dependabot 的 PR 只告诉你关于 *一个* 包的信息,但 lockfile 却悄悄更改了四十个。 `lockvet` 会读取实际的 lockfile diff,并告诉你到底发生了什么: - **升级了什么** —— 每一个被添加 / 移除 / 升级 / 降级的包, 分类为 major / minor / patch,且最关键的排在最前面 - **为何变动** —— 每个更改都会被标记为 `(direct)` 或 `via <引入它的依赖>`, 因此原本 40 个包的 diff 会浓缩为“一次直接升级及其附带依赖” - **什么有风险** —— 新版本 *引入* 的漏洞, 此次升级 *修复* 的漏洞,以及同时影响两者的安全公告(实时获取自 [OSV.dev](https://osv.dev),并在 GHSA/CVE/PYSEC 别名间进行去重) - **什么有可疑之处** —— 每个引入版本的发布时长,对过去 7 天内发布的任何内容标记 ⏱ (大多数被劫持的发布都会在几天内被发现 —— 一段冷却期就是低成本的保险),外加上游弃用通知(通过 [deps.dev](https://deps.dev)) - **跨所有生态系统,封装在单一静态二进制文件中** —— 支持 20 种 lockfile 格式: npm, pnpm, yarn (classic & berry), bun, Deno, Cargo, uv, poetry, pipenv, `requirements.txt`, Go modules, Composer, Bundler, Hex/mix, pub/Flutter, Gradle, NuGet, Swift Package Manager, CocoaPods, Nix flakes ## 示例 ``` $ lockvet HEAD~1 # what did that "upgrade express" commit really do? package-lock.json (npm) ↑ express 4.17.1 → 5.1.0 MAJOR (direct) (15mo old) ▼ fixes GHSA-rv95-896h-c2vc (moderate) Express.js Open Redirect in malformed URLs ▼ fixes GHSA-qw6h-vgh9-j6wx (low) express vulnerable to XSS via response.redirect() ↑ body-parser 1.19.0 → 2.3.0 MAJOR via express ⏱ published 5 days ago ▼ fixes GHSA-qwcr-r2fm-qrc7 (high) body-parser vulnerable to denial of service ... ↑ path-to-regexp 0.1.7 → 8.4.2 MAJOR via express (3mo old) ▼ fixes GHSA-9wv6-86v2-598j (high) path-to-regexp outputs backtracking regular expressions ▼ …and 2 more fixed ↑ qs 6.7.0 → 6.15.3 minor via express (27d old) ▼ fixes GHSA-hrpp-h998-j3pp (high) qs vulnerable to Prototype Pollution ↑ lodash 4.17.20 → 4.17.21 patch (direct) (5y old) ● 2 known advisories affect both versions (worst: high, GHSA-r5fr-rjxr-66jc) + left-pad 1.3.0 (added) (direct) (8y old) ● deprecated upstream: use String.prototype.padStart() - minimist 1.2.5 (removed) via mkdirp 64 packages changed · 21 major · 9 minor · 4 patch · 23 added · 7 removed · 3 direct · 61 transitive · vulnerabilities: 0 introduced, 15 fixed, 3 unresolved · 1 fresh (<7d old) · 1 deprecated ``` ## 安装 ``` go install github.com/matteo-sung/lockvet@latest ``` 或者从 [发布页面](https://github.com/matteo-sung/lockvet/releases) 获取预编译二进制文件 (Linux / macOS / Windows, amd64 & arm64): ``` curl -fsSL https://raw.githubusercontent.com/matteo-sung/lockvet/main/install.sh | sh ``` ## 用法 ``` lockvet # working tree vs HEAD — "what did I just do?" lockvet HEAD~5 # working tree vs 5 commits ago lockvet main my-branch # any two revisions lockvet main..my-branch # range syntax works too lockvet -md # markdown, ready to paste into a PR comment lockvet -json # machine-readable, full vuln ID lists lockvet -offline # no network calls (skips vuln + metadata lookups) lockvet -only jiff # one package's story: jiff itself plus everything # it dragged in (matches names AND via-chains; # globs ok: -only "@babel/*" or -only "*sys*") lockvet -fresh-days 14 # widen the "recently published" window (default 7) lockvet -fail-on major,vuln # CI gate: exit 1 on major bumps or new vulns lockvet -fail-on fresh # CI gate: enforce a release cooldown ``` 在任何 git 仓库中运行它。`lockvet` 会自动找到两个版本之间所有更改过的 lockfile —— 无需配置,也不需要“这是哪个包管理器”的清单。 ## 在 CI 中(自动审查 Dependabot/Renovate PR) `lockvet` 会在任何涉及 lockfile 的 PR 上发布摘要评论 — [在真实的 PR 中实时查看](https://github.com/matteo-sung/lockvet-demo/pull/1): ``` # .github/workflows/lockvet.yml name: lockvet on: pull_request: paths: - '**/package-lock.json' - '**/pnpm-lock.yaml' - '**/yarn.lock' - '**/bun.lock' - '**/Cargo.lock' - '**/uv.lock' - '**/poetry.lock' - '**/requirements.txt' - '**/go.mod' - '**/composer.lock' - '**/Gemfile.lock' permissions: pull-requests: write contents: read jobs: lockvet: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: matteo-sung/lockvet@v0.1.1 # optional: # with: # fail-on: vuln # or "major,vuln,downgrade,fresh,deprecated" # fresh-days: '7' # cooldown window for the fresh flag ``` ## 支持的 lockfile | 生态系统 | 文件 | |---|---| | JavaScript | `package-lock.json`, `npm-shrinkwrap.json`, `pnpm-lock.yaml`, `yarn.lock` (v1 & berry), `bun.lock`, `deno.lock` | | Rust | `Cargo.lock` | | Python | `uv.lock`, `poetry.lock`, `Pipfile.lock`, `requirements.txt` (`==` 版本锁定) | | Go | `go.mod` | | PHP | `composer.lock` | | Ruby | `Gemfile.lock` | | Elixir | `mix.lock` | | Dart / Flutter | `pubspec.lock` | | Java / JVM | `gradle.lockfile` | | .NET | `packages.lock.json` | | Swift | `Package.resolved` | | iOS / CocoaPods | `Podfile.lock` | | Nix | `flake.lock` | 注:当 lockfile 记录了其依赖图时,会出现 direct / `via …` 来源标签:npm, pnpm, yarn, Cargo, uv, poetry, Composer, Bundler 以及 Go modules (go.mod 的 `// indirect` 标记提供了 direct/transitive 信息,但不包含完整链条)。仅锁定扁平化版本的格式(`requirements.txt`, `mix.lock`, Gradle 等)会跳过该标签。 Deno 的 `jsr:` 包、CocoaPods 和 Nix flakes(目前)没有对应的 OSV.dev 生态系统,因此这些 diff 在解释时不会包含漏洞数据。 版本发布时长 / 弃用信息来自 deps.dev,它涵盖了 npm, crates.io, PyPI, Go, Maven, NuGet 和 RubyGems —— 其他生态系统将直接跳过此项检查。 Nix flake inputs 锁定的是 git revisions 而不是版本号 —— lockvet 会将它们显示为 `.`,这样 diff 依然可以按时间顺序阅读。 缺少你在意的格式吗?[提出一个 issue](https://github.com/matteo-sung/lockvet/issues) — 每种格式的解析器大约只需 50 行代码。 ## 工作原理 1. `git diff --name-only ` 找到更改过的 lockfile。 2. 每个 lockfile 版本都通过 `git show` 读取,并被解析为 `package → pinned versions`(支持处理单个包的多个版本 —— npm 嵌套,Cargo 重复的 major 版本)。 3. 对两个快照进行 diff,并使用一个宽松的版本解析器对每个更改进行分类,该解析器能够处理 semver、Python post-releases 和 Go pseudo-versions。 在 lockfile 同时记录了依赖边和根依赖的情况下,lockvet 会对图进行 BFS 遍历,将每个更改标记为 `(direct)` 或 `via ` —— 无需清单文件或网络。 4. 旧版本和新版本都会与 OSV.dev 的批量 API 进行比对。如果漏洞与 新版本匹配但不匹配旧版本,则是 **引入** 的漏洞;反之则是 **修复** 的漏洞;如果两者都匹配则为 **未解决**。具有别名属性的公告 (针对同一问题的 GHSA/CVE/PYSEC/RUSTSEC)会被合并。 5. 每一个 *引入的* 版本都会在 deps.dev 的批量 API 上查询其 发布日期和弃用状态 (npm, crates.io, PyPI, Go, Maven, NuGet, RubyGems)。发布时间少于 `-fresh-days`(默认值为 7 天)的版本会被标记 ⏱ —— 供应链攻击通常会在发布后的几天内被发现并撤回 (yanked),因此一段短暂的冷却期就能过滤掉大部分威胁。 **隐私:** 仅有的网络流量就是发往 OSV.dev 和 deps.dev 的批量 查询(包名 + 版本号)。`-offline` 会禁用两者;`-no-vulns` / `-no-meta` 可单独禁用它们。绝对没有任何遥测数据收集。 **依赖:** 无。纯 Go 标准库实现。 ## 工具对比 | | 对 lockfile 执行 `git diff` | [whatsdiff](https://github.com/whatsdiff/whatsdiff) v2.6 | **lockvet** | |---|---|---|---| | Lockfile 格式 | 任意 (原始文本) | 3 (composer, npm, pnpm) | 跨 14 个生态系统的 **20** 种 | | 直观的逐包摘要 | ✗ | ✓ | ✓ | | 变更引入 / 修复的漏洞 | ✗ | ✗ | ✓ (OSV.dev) | | 发布时长 + 针对新版本的 ⏱ 冷却标记 | ✗ | ✗ | ✓ (deps.dev) | | 弃用警告 | ✗ | ✗ | ✓ (deps.dev) | | 直接依赖 vs. 传递依赖,并带有引入链 (`via a › b`) | ✗ | ✗ | ✓ | | CI 门禁 | ✗ | 基于包的 `check` 退出代码 | 策略门禁 (`-fail-on major\|vuln\|fresh\|deprecated`) + GitHub Action | | 输出格式 | 文本 | 文本, JSON, markdown | 文本, JSON, markdown | | 更新的变更日志 / 发行说明 | ✗ | ✓ | ✗ | | 交互式 TUI,MCP 服务器 | ✗ | ✓ | ✗ | | 运行时 | — | PHP (提供二进制文件) | 单一的 Go 静态二进制文件,零依赖 | 如果你主要使用 composer/npm,并且需要变更日志和 TUI,whatsdiff 是一个很好的工具。 lockvet 的侧重点不同:*我应该信任这个 diff 吗?* —— 无论你的仓库使用的是什么语言,都能在 CI 中内联提供安全数据。 ## 非目标 - 不是一款完整的 SCA 扫描工具 —— [osv-scanner](https://github.com/google/osv-scanner) 会审查你的 *整个* 依赖树。lockvet 只是用来解释 *变更*。 - 不是一款更新工具 —— Dependabot/Renovate 负责提交 PR;lockvet 告诉你 是否应该合并它们。 - 不获取变更日志,也没有交互式 TUI(参见上文中的 whatsdiff)—— lockvet 始终是一个一次性命令,其输出可以直接作为 PR 评论发布。 ## 许可证 MIT © Matteo Sung
标签:EVTX分析, 日志审计